From 8da89464096cc91b2b4ab8e67e02ea8b989f7a59 Mon Sep 17 00:00:00 2001 From: Tao He Date: Sun, 22 May 2022 02:02:58 +0800 Subject: [PATCH] Refactor the implementation of sync-client and async-client. Signed-off-by: Tao He --- .github/workflows/build-test.yml | 12 + .gitignore | 3 + CMakeLists.txt | 11 +- README.md | 27 + etcd-cpp-api-config.in.cmake | 4 +- etcd/Client.hpp | 174 ++-- etcd/KeepAlive.hpp | 10 +- etcd/Response.hpp | 47 +- etcd/SyncClient.hpp | 730 +++++++++++++-- etcd/Value.hpp | 10 +- etcd/Watcher.hpp | 15 +- etcd/v3/Action.hpp | 9 +- etcd/v3/AsyncCompareAndDeleteAction.hpp | 2 +- etcd/v3/AsyncCompareAndSwapAction.hpp | 2 +- etcd/v3/AsyncDeleteAction.hpp | 2 +- etcd/v3/AsyncElectionAction.hpp | 12 +- etcd/v3/AsyncHeadAction.hpp | 2 +- etcd/v3/AsyncLeaseAction.hpp | 10 +- etcd/v3/AsyncLockAction.hpp | 4 +- etcd/v3/AsyncPutAction.hpp | 2 +- etcd/v3/AsyncRangeAction.hpp | 2 +- etcd/v3/AsyncSetAction.hpp | 2 +- etcd/v3/AsyncTxnAction.hpp | 2 +- etcd/v3/AsyncUpdateAction.hpp | 2 +- etcd/v3/AsyncWatchAction.hpp | 2 +- etcd/v3/Transaction.hpp | 2 +- etcd/v3/action_constants.hpp | 1 + src/CMakeLists.txt | 54 +- src/Client.cpp | 1089 +++++++--------------- src/KeepAlive.cpp | 20 +- src/SyncClient.cpp | 1103 ++++++++++++++++++++--- src/Watcher.cpp | 25 +- src/v3/Action.cpp | 28 +- src/v3/AsyncCompareAndDeleteAction.cpp | 4 +- src/v3/AsyncCompareAndSwapAction.cpp | 4 +- src/v3/AsyncDeleteAction.cpp | 4 +- src/v3/AsyncElectionAction.cpp | 127 +-- src/v3/AsyncHeadAction.cpp | 4 +- src/v3/AsyncLeaseAction.cpp | 20 +- src/v3/AsyncLockAction.cpp | 8 +- src/v3/AsyncPutAction.cpp | 4 +- src/v3/AsyncRangeAction.cpp | 6 +- src/v3/AsyncSetAction.cpp | 4 +- src/v3/AsyncTxnAction.cpp | 4 +- src/v3/AsyncUpdateAction.cpp | 4 +- src/v3/AsyncWatchAction.cpp | 4 +- src/v3/action_constants.cpp | 1 + tst/EtcdTest.cpp | 6 - tst/LockTest.cpp | 11 +- tst/RewatchTest.cpp | 3 +- 50 files changed, 2402 insertions(+), 1236 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 91b45f0..8079393 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -159,11 +159,23 @@ jobs: sleep 5 # tests without auth + + echo "Run the etcd sync test ........................." ./build/bin/EtcdSyncTest + + echo "Run the etcd test ........................." ./build/bin/EtcdTest + + echo "Run the etcd lock test ........................." ./build/bin/LockTest + + echo "Run the etcd memory leak test ........................." ./build/bin/MemLeakTest + + echo "Run the etcd watcher test ........................." ./build/bin/WatcherTest + + echo "Run the etcd election test ........................." ./build/bin/ElectionTest killall -TERM etcd diff --git a/.gitignore b/.gitignore index 859722c..6ffd637 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ compile_commands.json proto/**/*.pb.cc proto/**/*.pb.h default.etcd/ + +# vscode-clangd +.cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c523d1..ca3eb7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,8 +26,10 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) ) endif() -option(BUILD_SHARED_LIBS "Build shared libraries" ON) -option(BUILD_ETCD_TESTS "Build test cases" OFF) +option(BUILD_SHARED_LIBS "Build etcd-cpp-apiv3 shared libraries" ON) +option(BUILD_ETCD_TESTS "Build etcd-cpp-apiv3 test cases" OFF) +option(CMAKE_POSITION_INDEPENDENT_CODE "Build etcd-cpp-apiv3 with -fPIC" ON) +option(ETCD_W_STRICT "Build etcd-cpp-apiv3 with -Werror" ON) # reference: https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling#always-full-rpath set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) @@ -98,7 +100,10 @@ include_directories(SYSTEM ${Boost_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Werror -Wno-string-compare") + if(ETCD_W_STRICT) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") + endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Wno-string-compare") endif() check_cxx_compiler_flag(-Wno-c++17-extensions W_NO_CPP17_EXTENSIONS) diff --git a/README.md b/README.md index c3772f9..f1fbb17 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,33 @@ dependencies have been successfully installed: The _etcd-cpp-apiv3_ should work well with etcd > 3.0. Feel free to issue an issue to us on Github when you encounter problems when working with etcd 3.x releases. +## Sync vs. Async runtime + +There are various discussion about whether to support a user-transparent multi-thread executor in +the background, or, leaving the burden of thread management to the user (e.g., see +[issue#100](https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/issues/100) for more discussion about +the implementation of underlying thread model). + +The _etcd-cpp-apiv3_ library supports both synchronous and asynchronous runtime, in two separate +library as follows: + +- etcd-cpp-api-core: the synchronous runtime, provides a blocking-style API and the users are responsible + for handling dispatch the request into separate thread to avoid been blocked by waiting for response. + - target found by cmake: `ETCD_CPP_CORE_LIBRARIES` + +- etcd-cpp-api: the asynchronous runtime backed by the `pplx` library from + [cpprestsdk](https://github.com/microsoft/cpprestsdk), where a `boost::asio::io_context` and a pool + of threads is used to run the asynchronous operations in the background. By default the number of + threads in the thread pool equals to `std::thread::hardware_concurrency()`. + - target found by cmake: `ETCD_CPP_LIBRARIES` + +We encourage the users to use the asynchronous runtime by default, as it provides more flexibility +and convenient APIs and less possibilities for errors that block the main thread. However, please note +that the asynchronous runtime will setup a thread pool in the background. + +Note that `etcd-cpp-api-core` and `etcd-cpp-api` are two separate target and don't depends on each other. +You should depends on either of them in your program. + ## Usage ```c++ diff --git a/etcd-cpp-api-config.in.cmake b/etcd-cpp-api-config.in.cmake index 4cf1652..befba7a 100644 --- a/etcd-cpp-api-config.in.cmake +++ b/etcd-cpp-api-config.in.cmake @@ -14,6 +14,7 @@ if(NOT gRPC_FOUND) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) find_dependency(GRPC) endif() + find_dependency(cpprestsdk) if(cpprestsdk_FOUND) set(CPPREST_LIB cpprestsdk::cpprest) @@ -24,11 +25,12 @@ include("${CMAKE_CURRENT_LIST_DIR}/etcd-targets.cmake") set(etcd-cpp-api_FOUND TRUE) set(ETCD_CPP_LIBRARIES etcd-cpp-api) +set(ETCD_CPP_CORE_LIBRARIES etcd-cpp-api-core) set(ETCD_CPP_INCLUDE_DIR "${ETCD_CPP_HOME}/include") set(ETCD_CPP_INCLUDE_DIRS "${ETCD_CPP_INCLUDE_DIR}") include(FindPackageMessage) find_package_message(etcd "Found etcd: ${CMAKE_CURRENT_LIST_FILE} (found version \"@etcd-cpp-api_VERSION@\")" - "etcd-cpp-apiv3 version: @etcd-cpp-api_VERSION@\netcd-cpp-apiv3 libraries: ${ETCD_CPP_LIBRARIES}, include directories: ${ETCD_CPP_INCLUDE_DIRS}" + "etcd-cpp-apiv3 version: @etcd-cpp-api_VERSION@\netcd-cpp-apiv3 libraries: ${ETCD_CPP_LIBRARIES}, \netcd-cpp-apiv3 core libraries: ${ETCD_CPP_CORE_LIBRARIES}\ninclude directories: ${ETCD_CPP_INCLUDE_DIRS}" ) diff --git a/etcd/Client.hpp b/etcd/Client.hpp index 1e450a0..e07b260 100644 --- a/etcd/Client.hpp +++ b/etcd/Client.hpp @@ -5,56 +5,38 @@ #include #include #include +#include #include "pplx/pplxtasks.h" #include "etcd/Response.hpp" +#include "etcd/SyncClient.hpp" #include "etcd/v3/action_constants.hpp" -namespace etcdv3 { - class Transaction; - class AsyncObserveAction; - - namespace detail { - std::string string_plus_one(std::string const &value); - } -} - -#if defined(WITH_GRPC_CHANNEL_CLASS) -namespace grpc { - class Channel; - class ChannelArguments; -} -#else -namespace grpc_impl { - class Channel; - class ChannelArguments; -} -#endif - namespace etcd { - using etcdv3::ERROR_KEY_NOT_FOUND; - using etcdv3::ERROR_COMPARE_FAILED; - using etcdv3::ERROR_KEY_ALREADY_EXISTS; - - class KeepAlive; - class Watcher; - + // FIXME /** * Client is responsible for maintaining a connection towards an etcd server. * Etcd operations can be reached via the methods of the client. */ class Client { - private: - class TokenAuthenticator; - class TokenAuthenticatorDeleter { - public: - void operator()(TokenAuthenticator *authenticator); - }; - public: + /** + * Constructs an async etcd client object from an established synchronous client. + * + * @param sync_client The synchronous client to use for the async client. + */ + Client(SyncClient *client); + + /** + * Constructs an async etcd client object from an established synchronous client. + * + * @param sync_client The synchronous client to use for the async client. + */ + static Client* WithClient(SyncClient *client); + /** * Constructs an etcd client object. * @@ -87,7 +69,7 @@ namespace etcd * or multiple url, seperated by ',' or ';'. * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. */ - static etcd::Client *WithUrl(std::string const & etcd_url, + static Client *WithUrl(std::string const & etcd_url, std::string const & load_balancer = "round_robin"); /** @@ -97,7 +79,7 @@ namespace etcd * or multiple url, seperated by ',' or ';'. * @param arguments user provided grpc channel arguments. */ - static etcd::Client *WithUrl(std::string const & etcd_url, + static Client *WithUrl(std::string const & etcd_url, #if defined(WITH_GRPC_CHANNEL_CLASS) grpc::ChannelArguments const & arguments #else @@ -143,7 +125,6 @@ namespace etcd #endif ); - /** * Constructs an etcd client object. * @@ -154,7 +135,7 @@ namespace etcd * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. */ - static etcd::Client *WithUser(std::string const & etcd_url, + static Client *WithUser(std::string const & etcd_url, std::string const & username, std::string const & password, int const auth_token_ttl = 300, @@ -171,7 +152,7 @@ namespace etcd * @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. * Default value should be 300. */ - static etcd::Client *WithUser(std::string const & etcd_url, + static Client *WithUser(std::string const & etcd_url, std::string const & username, std::string const & password, int const auth_token_ttl, @@ -182,7 +163,6 @@ namespace etcd #endif ); - /** * Constructs an etcd client object. * @@ -222,7 +202,6 @@ namespace etcd #endif ); - /** * Constructs an etcd client object. * @@ -236,7 +215,7 @@ namespace etcd * SANS of your SSL certificate. * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. */ - static etcd::Client *WithSSL(std::string const & etcd_url, + static Client *WithSSL(std::string const & etcd_url, std::string const & ca, std::string const & cert = "", std::string const & key = "", @@ -256,7 +235,7 @@ namespace etcd * SANS of your SSL certificate. * @param arguments user provided grpc channel arguments. */ - static etcd::Client *WithSSL(std::string const & etcd_url, + static Client *WithSSL(std::string const & etcd_url, #if defined(WITH_GRPC_CHANNEL_CLASS) grpc::ChannelArguments const & arguments, #else @@ -267,6 +246,8 @@ namespace etcd std::string const & key = "", std::string const & target_name_override = ""); + ~Client(); + /** * Get the HEAD revision of the connected etcd server. */ @@ -398,6 +379,30 @@ namespace etcd */ pplx::task ls(std::string const & key); + /** + * Removes a directory node. Fails if the parent directory dos not exists or not a directory. + * @param key is the directory to be created to be listed + * @param recursive if true then delete a whole subtree, otherwise deletes only an empty directory. + */ + pplx::task rmdir(std::string const & key, bool recursive = false); + + /** + * Removes multiple keys between [key, range_end). + * + * This overload for `const char *` is to avoid const char * to bool implicit casting. + * + * @param key is the directory to be created to be listed + * @param range_end is the end of key range to be removed. + */ + pplx::task rmdir(std::string const & key, const char *range_end); + + /** + * Removes multiple keys between [key, range_end). + * + * @param key is the directory to be created to be listed + * @param range_end is the end of key range to be removed. + */ + pplx::task rmdir(std::string const & key, std::string const &range_end); /** * Gets a directory listing of the directory identified by the key. @@ -428,31 +433,6 @@ namespace etcd */ pplx::task ls(std::string const & key, std::string const &range_end, size_t const limit); - /** - * Removes a directory node. Fails if the parent directory dos not exists or not a directory. - * @param key is the directory to be created to be listed - * @param recursive if true then delete a whole subtree, otherwise deletes only an empty directory. - */ - pplx::task rmdir(std::string const & key, bool recursive = false); - - /** - * Removes multiple keys between [key, range_end). - * - * This overload for `const char *` is to avoid const char * to bool implicit casting. - * - * @param key is the directory to be created to be listed - * @param range_end is the end of key range to be removed. - */ - pplx::task rmdir(std::string const & key, const char *range_end); - - /** - * Removes multiple keys between [key, range_end). - * - * @param key is the directory to be created to be listed - * @param range_end is the end of key range to be removed. - */ - pplx::task rmdir(std::string const & key, std::string const &range_end); - /** * Watches for changes of a key or a subtree. Please note that if you watch e.g. "/testdir" and * a new key is created, like "/testdir/newkey" then no change happened in the value of @@ -597,19 +577,7 @@ namespace etcd */ pplx::task leader(std::string const &name); - /** - * An observer that will cancel the associated election::observe request - * when being destruct. - */ - class Observer { - public: - ~Observer(); - private: - std::shared_ptr action = nullptr; - pplx::task resp; - - friend class Client; - }; + using Observer = SyncClient::Observer; /** * Observe the leader change. @@ -618,19 +586,7 @@ namespace etcd * * @returns an observer that holds that action and will cancel the request when being destructed. */ - std::unique_ptr observe(std::string const &name, - const bool once = false); - - /** - * Observe the leader change. - * - * @param name is the names of election to watch. - * - * @returns an observer that holds that action and will cancel the request when being destructed. - */ - std::unique_ptr observe(std::string const &name, - std::function callback, - const bool once = false); + std::unique_ptr observe(std::string const &name); /** * Updates the value of election with a new value, with leader key returns by @@ -649,27 +605,23 @@ namespace etcd */ const std::string ¤t_auth_token() const; - private: + /** + * Obtain the underlying gRPC channel. + */ #if defined(WITH_GRPC_CHANNEL_CLASS) - std::shared_ptr channel; + std::shared_ptr grpc_channel() const; #else - std::shared_ptr channel; + std::shared_ptr grpc_channel() const; #endif - mutable std::unique_ptr token_authenticator; + /** + * Obtain the underlying synchronous client. + */ + SyncClient* sync_client() const; - struct EtcdServerStubs; - struct EtcdServerStubsDeleter { - void operator()(EtcdServerStubs *stubs); - }; - std::unique_ptr stubs; - - std::mutex mutex_for_keepalives; - std::map leases_for_locks; - std::map> keep_alive_for_locks; - - friend class KeepAlive; - friend class Watcher; + private: + bool own_client = true; + SyncClient *client = nullptr; }; } diff --git a/etcd/KeepAlive.hpp b/etcd/KeepAlive.hpp index 79fe637..758c277 100644 --- a/etcd/KeepAlive.hpp +++ b/etcd/KeepAlive.hpp @@ -7,7 +7,7 @@ #include #include -#include "etcd/Client.hpp" +#include "etcd/SyncClient.hpp" #include "etcd/Response.hpp" #include @@ -20,6 +20,9 @@ namespace etcd { + // forward declaration to avoid header/library dependency + class Client; + /** * If ID is set to 0, the library will choose an ID, and can be accessed from ".Lease()". */ @@ -28,6 +31,8 @@ namespace etcd public: KeepAlive(Client const &client, int ttl, int64_t lease_id = 0); + KeepAlive(SyncClient const &client, + int ttl, int64_t lease_id = 0); KeepAlive(std::string const & address, int ttl, int64_t lease_id = 0); KeepAlive(std::string const & address, @@ -38,6 +43,9 @@ namespace etcd KeepAlive(Client const &client, std::function const &handler, int ttl, int64_t lease_id = 0); + KeepAlive(SyncClient const &client, + std::function const &handler, + int ttl, int64_t lease_id = 0); KeepAlive(std::string const & address, std::function const &handler, int ttl, int64_t lease_id = 0); diff --git a/etcd/Response.hpp b/etcd/Response.hpp index 290fd22..5e9dc26 100644 --- a/etcd/Response.hpp +++ b/etcd/Response.hpp @@ -1,12 +1,13 @@ #ifndef __ETCD_RESPONSE_HPP__ #define __ETCD_RESPONSE_HPP__ +#include #include +#include +#include #include #include -#include "pplx/pplxtasks.h" - #include "etcd/Value.hpp" namespace etcdv3 { @@ -28,39 +29,42 @@ namespace etcd public: template - static pplx::task create(std::shared_ptr call) + static etcd::Response create(std::unique_ptr call) { - return pplx::task([call]() - { call->waitForResponse(); auto v3resp = call->ParseResponse(); auto duration = std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - call->startTimepoint()); return etcd::Response(v3resp, duration); - }); } template - static pplx::task create(std::shared_ptr call, - std::function callback) + static etcd::Response create(std::shared_ptr call) + { + call->waitForResponse(); + auto v3resp = call->ParseResponse(); + + auto duration = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - call->startTimepoint()); + return etcd::Response(v3resp, duration); + } + + template + static etcd::Response create(std::unique_ptr call, + std::function callback) { - return pplx::task([call, callback]() - { call->waitForResponse(callback); auto v3resp = call->ParseResponse(); auto duration = std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - call->startTimepoint()); return etcd::Response(v3resp, duration); - }); } template - static pplx::task create(std::function()> callfn) + static etcd::Response create(std::function()> callfn) { - return pplx::task([callfn]() - { auto call = callfn(); call->waitForResponse(); @@ -69,18 +73,19 @@ namespace etcd auto duration = std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - call->startTimepoint()); return etcd::Response(v3resp, duration); - }); } template - static etcd::Response create_sync(std::shared_ptr call) + static etcd::Response create(std::function()> callfn) { - call->waitForResponse(); - auto v3resp = call->ParseResponse(); + auto call = callfn(); - auto duration = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now() - call->startTimepoint()); - return etcd::Response(v3resp, duration); + call->waitForResponse(); + auto v3resp = call->ParseResponse(); + + auto duration = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - call->startTimepoint()); + return etcd::Response(v3resp, duration); } Response(); diff --git a/etcd/SyncClient.hpp b/etcd/SyncClient.hpp index 0a2fee9..94f7bab 100644 --- a/etcd/SyncClient.hpp +++ b/etcd/SyncClient.hpp @@ -1,10 +1,76 @@ #ifndef __ETCD_SYNC_CLIENT_HPP__ #define __ETCD_SYNC_CLIENT_HPP__ -#include "etcd/Client.hpp" +#include +#include +#include +#include + +#include "etcd/Response.hpp" +#include "etcd/v3/action_constants.hpp" + +namespace etcdv3 { + struct ActionParameters; + + class AsyncCompareAndDeleteAction; + class AsyncCompareAndSwapAction; + class AsyncDeleteAction; + class AsyncCampaignAction; + class AsyncProclaimAction; + class AsyncLeaderAction; + class AsyncObserveAction; + class AsyncResignAction; + class AsyncHeadAction; + class AsyncLeaseGrantAction; + class AsyncLeaseRevokeAction; + class AsyncLeaseKeepAliveAction; + class AsyncLeaseTimeToLiveAction; + class AsyncLeaseLeasesAction; + class AsyncLockAction; + class AsyncUnlockAction; + class AsyncPutAction; + class AsyncRangeAction; + class AsyncSetAction; + class AsyncTxnAction; + class AsyncUpdateAction; + class AsyncWatchAction; + + enum class AtomicityType; + class Transaction; + + namespace detail { + std::string string_plus_one(std::string const &value); + } +} + +#if defined(WITH_GRPC_CHANNEL_CLASS) +namespace grpc { + class Channel; + class ChannelArguments; +} +#else +namespace grpc_impl { + class Channel; + class ChannelArguments; +} +#endif namespace etcd { + using etcdv3::ERROR_KEY_NOT_FOUND; + using etcdv3::ERROR_COMPARE_FAILED; + using etcdv3::ERROR_KEY_ALREADY_EXISTS; + using etcdv3::ERROR_ACTION_CANCELLED; + + class KeepAlive; + class Watcher; + class Client; + + // FIXME + /** + * Client is responsible for maintaining a connection towards an etcd server. + * Etcd operations can be reached via the methods of the client. + */ /** * SyncClient is a wrapper around etcd::Client and provides a simplified sync interface with blocking operations. * @@ -15,19 +81,66 @@ namespace etcd */ class SyncClient { + private: + class TokenAuthenticator; + class TokenAuthenticatorDeleter { + public: + void operator()(TokenAuthenticator *authenticator); + }; + public: /** - * Constructs an etcd sync client object. + * Constructs an etcd client object. * * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * or multiple url, seperated by ',' or ';'. * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. */ SyncClient(std::string const & etcd_url, - std::string const & load_balancer = "round_robin"); + std::string const & load_balancer = "round_robin"); /** - * Constructs an etcd sync client object. + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param arguments user provided grpc channel arguments. + */ + SyncClient(std::string const & etcd_url, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ); + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. + */ + static SyncClient *WithUrl(std::string const & etcd_url, + std::string const & load_balancer = "round_robin"); + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param arguments user provided grpc channel arguments. + */ + static SyncClient *WithUrl(std::string const & etcd_url, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ); + + /** + * Constructs an etcd client object. * * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * or multiple url, seperated by ',' or ';'. @@ -37,51 +150,341 @@ namespace etcd * @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. */ SyncClient(std::string const & etcd_url, - std::string const & username, - std::string const & password, - int const auth_token_ttl = 300, - std::string const & load_balancer = "round_robin"); + std::string const & username, + std::string const & password, + int const auth_token_ttl = 300, + std::string const & load_balancer = "round_robin"); + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param username username of etcd auth + * @param password password of etcd auth + * @param arguments user provided grpc channel arguments. + * @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. + * Default value should be 300. + */ + SyncClient(std::string const & etcd_url, + std::string const & username, + std::string const & password, + int const auth_token_ttl, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ); + + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param username username of etcd auth + * @param password password of etcd auth + * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. + * @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. + */ + static SyncClient *WithUser(std::string const & etcd_url, + std::string const & username, + std::string const & password, + int const auth_token_ttl = 300, + std::string const & load_balancer = "round_robin"); + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param username username of etcd auth + * @param password password of etcd auth + * @param arguments user provided grpc channel arguments. + * @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. + * Default value should be 300. + */ + static SyncClient *WithUser(std::string const & etcd_url, + std::string const & username, + std::string const & password, + int const auth_token_ttl, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ); + + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param ca root CA file for SSL/TLS connection. + * @param cert cert chain file for SSL/TLS authentication, could be empty string. + * @param key private key file for SSL/TLS authentication, could be empty string. + * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. + */ + SyncClient(std::string const & etcd_url, + std::string const & ca, + std::string const & cert, + std::string const & key, + std::string const & target_name_override, + std::string const & load_balancer); + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param ca root CA file for SSL/TLS connection. + * @param cert cert chain file for SSL/TLS authentication, could be empty string. + * @param key private key file for SSL/TLS authentication, could be empty string. + * @param arguments user provided grpc channel arguments. + */ + SyncClient(std::string const & etcd_url, + std::string const & ca, + std::string const & cert, + std::string const & key, + std::string const & target_name_override, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ); + + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param ca root CA file for SSL/TLS connection. + * @param cert cert chain file for SSL/TLS authentication, could be empty string. + * @param key private key file for SSL/TLS authentication, could be empty string. + * @param target_name_override Override the target host name if you want to pass multiple address + * for load balancing with SSL, and there's no DNS. The @target_name_override@ must exist in the + * SANS of your SSL certificate. + * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. + */ + static SyncClient *WithSSL(std::string const & etcd_url, + std::string const & ca, + std::string const & cert = "", + std::string const & key = "", + std::string const & target_name_override = "", + std::string const & load_balancer = "round_robin"); + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param ca root CA file for SSL/TLS connection. + * @param cert cert chain file for SSL/TLS authentication, could be empty string. + * @param key private key file for SSL/TLS authentication, could be empty string. + * @param target_name_override Override the target host name if you want to pass multiple address + * for load balancing with SSL, and there's no DNS. The @target_name_override@ must exist in the + * SANS of your SSL certificate. + * @param arguments user provided grpc channel arguments. + */ + static SyncClient *WithSSL(std::string const & etcd_url, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments, +#else + grpc_impl::ChannelArguments const & arguments, +#endif + std::string const & ca, + std::string const & cert = "", + std::string const & key = "", + std::string const & target_name_override = ""); + + ~SyncClient(); + + /** + * Get the HEAD revision of the connected etcd server. + */ Response head(); - Response get(std::string const & key); - Response set(std::string const & key, std::string const & value, int ttl = 0); - Response set(std::string const & key, std::string const & value, int64_t leaseId); - Response add(std::string const & key, std::string const & value, int ttl = 0); - Response add(std::string const & key, std::string const & value, int64_t leaseId); - Response put(std::string const & key, std::string const & value); - Response modify(std::string const & key, std::string const & value, int ttl = 0); - Response modify(std::string const & key, std::string const & value, int64_t leaseId); - Response modify_if(std::string const & key, std::string const & value, std::string const & old_value, int ttl = 0); - Response modify_if(std::string const & key, std::string const & value, std::string const & old_value, int64_t leaseId); - Response modify_if(std::string const & key, std::string const & value, int64_t old_index, int ttl = 0); - Response modify_if(std::string const & key, std::string const & value, int64_t old_index, int64_t leaseId); - Response rm(std::string const & key); - Response rm_if(std::string const & key, std::string const & old_value); - Response rm_if(std::string const & key, int64_t old_index); - Response ls(std::string const & key); - Response ls(std::string const & key, size_t const limit); - Response ls(std::string const & key, std::string const &range_end); - Response ls(std::string const & key, std::string const &range_end, size_t const limit); - Response mkdir(std::string const & key, int ttl = 0); - Response rmdir(std::string const & key, bool recursive = false); - Response rmdir(std::string const & key, const char *range_end); - Response rmdir(std::string const & key, std::string const &range_end); - Response leasegrant(int ttl); - Response leaserevoke(int64_t lease_id); - Response leasetimetolive(int64_t lease_id); - Response campaign(std::string const &name, int64_t lease_id, - std::string const &value); - Response proclaim(std::string const &name, int64_t lease_id, - std::string const &key, int64_t revision, std::string const &value); - Response leader(std::string const &name); - std::unique_ptr observe(std::string const &name, - const bool once = false); - std::unique_ptr observe(std::string const &name, - std::function callback, - const bool once = false); - Response resign(std::string const &name, int64_t lease_id, - std::string const &key, int64_t revision); + /** + * Get the value of specified key from the etcd server + * @param key is the key to be read + */ + Response get(std::string const & key); + + /** + * Sets the value of a key. The key will be modified if already exists or created + * if it does not exists. + * @param key is the key to be created or modified + * @param value is the new value to be set + */ + Response set(std::string const & key, std::string const & value, int ttl = 0); + + /** + * Sets the value of a key. The key will be modified if already exists or created + * if it does not exists. + * @param key is the key to be created or modified + * @param value is the new value to be set + * @param leaseId is the lease attached to the key + */ + Response set(std::string const & key, std::string const & value, int64_t leaseId); + + /** + * Creates a new key and sets it's value. Fails if the key already exists. + * @param key is the key to be created + * @param value is the value to be set + */ + Response add(std::string const & key, std::string const & value, int ttl = 0); + + /** + * Creates a new key and sets it's value. Fails if the key already exists. + * @param key is the key to be created + * @param value is the value to be set + * @param leaseId is the lease attached to the key + */ + Response add(std::string const & key, std::string const & value, int64_t leaseId); + + /** + * Put a new key-value pair. + * @param key is the key to be put + * @param value is the value to be put + */ + Response put(std::string const & key, std::string const & value); + + /** + * Modifies an existing key. Fails if the key does not exists. + * @param key is the key to be modified + * @param value is the new value to be set + */ + Response modify(std::string const & key, std::string const & value, int ttl = 0); + + /** + * Modifies an existing key. Fails if the key does not exists. + * @param key is the key to be modified + * @param value is the new value to be set + * @param leaseId is the lease attached to the key + */ + Response modify(std::string const & key, std::string const & value, int64_t leaseId); + + /** + * Modifies an existing key only if it has a specific value. Fails if the key does not exists + * or the original value differs from the expected one. + * @param key is the key to be modified + * @param value is the new value to be set + * @param old_value is the value to be replaced + */ + Response modify_if(std::string const & key, std::string const & value, std::string const & old_value, int ttl = 0); + + /** + * Modifies an existing key only if it has a specific value. Fails if the key does not exists + * or the original value differs from the expected one. + * @param key is the key to be modified + * @param value is the new value to be set + * @param old_value is the value to be replaced + * @param leaseId is the lease attached to the key + */ + Response modify_if(std::string const & key, std::string const & value, std::string const & old_value, int64_t leaseId); + + /** + * Modifies an existing key only if it has a specific modification index value. Fails if the key + * does not exists or the modification index of the previous value differs from the expected one. + * @param key is the key to be modified + * @param value is the new value to be set + * @param old_index is the expected index of the original value + */ + Response modify_if(std::string const & key, std::string const & value, int64_t old_index, int ttl = 0); + + /** + * Modifies an existing key only if it has a specific modification index value. Fails if the key + * does not exists or the modification index of the previous value differs from the expected one. + * @param key is the key to be modified + * @param value is the new value to be set + * @param old_index is the expected index of the original value + * @param leaseId is the lease attached to the key + */ + Response modify_if(std::string const & key, std::string const & value, int64_t old_index, int64_t leaseId); + + /** + * Removes a single key. The key has to point to a plain, non directory entry. + * @param key is the key to be deleted + */ + Response rm(std::string const & key); + + /** + * Removes a single key but only if it has a specific value. Fails if the key does not exists + * or the its value differs from the expected one. + * @param key is the key to be deleted + */ + Response rm_if(std::string const & key, std::string const & old_value); + + /** + * Removes an existing key only if it has a specific modification index value. Fails if the key + * does not exists or the modification index of it differs from the expected one. + * @param key is the key to be deleted + * @param old_index is the expected index of the existing value + */ + Response rm_if(std::string const & key, int64_t old_index); + + /** + * Removes a directory node. Fails if the parent directory dos not exists or not a directory. + * @param key is the directory to be created to be listed + * @param recursive if true then delete a whole subtree, otherwise deletes only an empty directory. + */ + Response rmdir(std::string const & key, bool recursive = false); + + /** + * Removes multiple keys between [key, range_end). + * + * This overload for `const char *` is to avoid const char * to bool implicit casting. + * + * @param key is the directory to be created to be listed + * @param range_end is the end of key range to be removed. + */ + Response rmdir(std::string const & key, const char *range_end); + + /** + * Removes multiple keys between [key, range_end). + * + * @param key is the directory to be created to be listed + * @param range_end is the end of key range to be removed. + */ + Response rmdir(std::string const & key, std::string const &range_end); + + /** + * Gets a directory listing of the directory identified by the key. + * @param key is the key to be listed + */ + Response ls(std::string const & key); + + /** + * Gets a directory listing of the directory identified by the key. + * @param key is the key to be listed + * @param limit is the size limit of results to be listed, we don't use default parameters + * to ensure backwards binary compatibility. + */ + Response ls(std::string const & key, size_t const limit); + + /** + * Gets a directory listing of the directory identified by the key and range_end, i.e., get + * all keys in the range [key, range_end). + * + * @param key is the key to be listed + * @param range_end is the end of key range to be listed + */ + Response ls(std::string const & key, std::string const &range_end); + + /** + * Gets a directory listing of the directory identified by the key and range_end, i.e., get + * all keys in the range [key, range_end). + * + * @param key is the key to be listed + * @param range_end is the end of key range to be listed + * @param limit is the size limit of results to be listed, we don't use default parameters + * to ensure backwards binary compatibility. + */ + Response ls(std::string const & key, std::string const &range_end, size_t const limit); /** * Watches for changes of a key or a subtree. Please note that if you watch e.g. "/testdir" and @@ -92,8 +495,6 @@ namespace etcd * @param recursive if true watch a whole subtree */ Response watch(std::string const & key, bool recursive = false); - Response watch(std::string const & key, const char *range_end); - Response watch(std::string const & key, std::string const &range_end); /** * Watches for changes of a key or a subtree from a specific index. The index value can be in the "past". @@ -102,11 +503,238 @@ namespace etcd * @param recursive if true watch a whole subtree */ Response watch(std::string const & key, int64_t fromIndex, bool recursive = false); + + /** + * Watches for changes of a range of keys inside [key, range_end). + * + * This overload for `const char *` is to avoid const char * to bool implicit casting. + * + * @param key is the value or directory to be watched + * @param range_end is the end of key range to be removed. + */ + Response watch(std::string const & key, const char *range_end); + + /** + * Watches for changes of a range of keys inside [key, range_end). + * + * @param key is the value or directory to be watched + * @param range_end is the end of key range to be removed. + */ + Response watch(std::string const & key, std::string const &range_end); + + /** + * Watches for changes of a range of keys inside [key, range_end) from a specific index. The index value + * can be in the "past". + * + * Watches for changes of a key or a subtree from a specific index. The index value can be in the "past". + * @param key is the value or directory to be watched + * @param range_end is the end of key range to be removed. + * @param fromIndex the first index we are interested in + */ Response watch(std::string const & key, std::string const &range_end, int64_t fromIndex); - protected: - Client client; - }; + /** + * Grants a lease. + * @param ttl is the time to live of the lease + */ + Response leasegrant(int ttl); + + /** + * Grants a lease. + * @param ttl is the time to live of the lease + */ + std::shared_ptr leasekeepalive(int ttl); + + /** + * Revoke a lease. + * @param lease_id is the id the lease + */ + Response leaserevoke(int64_t lease_id); + + /** + * Get time-to-live of a lease. + * @param lease_id is the id the lease + */ + Response leasetimetolive(int64_t lease_id); + + /** + * Gains a lock at a key, using a default created lease, using the default lease (10 seconds), with + * keeping alive has already been taken care of by the library. + * @param key is the key to be used to request the lock. + */ + Response lock(std::string const &key); + + /** + * Gains a lock at a key, using a default created lease, using the specified lease TTL (in seconds), with + * keeping alive has already been taken care of by the library. + * @param key is the key to be used to request the lock. + * @param lease_ttl is the TTL used to create a lease for the key. + */ + Response lock(std::string const &key, int lease_ttl); + + /** + * Gains a lock at a key, using a user-provided lease, the lifetime of the lease won't be taken care + * of by the library. + * @param key is the key to be used to request the lock. + */ + Response lock_with_lease(std::string const &key, int64_t lease_id); + + /** + * Releases a lock at a key. + * @param key is the lock key to release. + */ + Response unlock(std::string const &lock_key); + + /** + * Execute a etcd transaction. + * @param txn is the transaction object to be executed. + */ + Response txn(etcdv3::Transaction const &txn); + + /** + * Campaign for the election @name@. + * + * @param name is the name of election that will campaign for. + * @param lease_id is a user-managed (usually with a `KeepAlive`) lease id. + * @param value is the value for campaign. + * + * @returns a leader key if succeed, consist of + * + * - name: the name of the election + * - key: a generated election key + * - created rev: the revision of the generated key + * - lease: the lease id of the election leader + */ + Response campaign(std::string const &name, int64_t lease_id, std::string const &value); + + /** + * Updates the value of election with a new value, with leader key returns by + * @campaign@. + * + * @param name is the name of election + * @param lease_id is the user-provided lease id for the proclamation + * @param key is the generated associated key returned by @campaign@ + * @param revision is the created revision of key-value returned by @campaign@ + * @param value is the new value to set. + */ + Response proclaim(std::string const &name, int64_t lease_id, + std::string const &key, int64_t revision, std::string const &value); + + /** + * Get the current leader proclamation. + * + * @param name is the names of election. + * + * @returns current election key and value. + */ + Response leader(std::string const &name); + + /** + * An observer that will cancel the associated election::observe request + * when being destruct. + */ + class Observer { + public: + ~Observer(); + // wait a at least one response from the observer. + Response WaitOnce(); + private: + std::shared_ptr action = nullptr; + + friend class SyncClient; + }; + + /** + * Observe the leader change. + * + * @param name is the names of election to watch. + * + * @returns an observer that holds that action and will cancel the request when being destructed. + */ + std::unique_ptr observe(std::string const &name); + + /** + * Updates the value of election with a new value, with leader key returns by + * @campaign@. + * + * @param name is the name of election + * @param lease_id is the user-provided lease id for the proclamation + * @param key is the generated associated key returned by @campaign@ + * @param revision is the created revision of key-value returned by @campaign@ + */ + Response resign(std::string const &name, int64_t lease_id, + std::string const &key, int64_t revision); + + private: + // TODO: use std::unique_ptr<> + std::shared_ptr head_internal(); + std::shared_ptr get_internal(std::string const & key); + std::shared_ptr set_internal(std::string const & key, std::string const & value, int64_t leaseId); + std::shared_ptr add_internal(std::string const & key, std::string const & value, int64_t leaseId); + std::shared_ptr put_internal(std::string const & key, std::string const & value); + std::shared_ptr modify_internal(std::string const & key, std::string const & value, int64_t leaseId); + std::shared_ptr modify_if_internal(std::string const & key, std::string const & value, int64_t old_index, std::string const & old_value, int64_t leaseId, etcdv3::AtomicityType const & atomicity_type); + std::shared_ptr rm_internal(std::string const & key); + std::shared_ptr rm_if_internal(std::string const & key, int64_t old_index, const std::string & old_value, etcdv3::AtomicityType const & atomicity_type); + std::shared_ptr rmdir_internal(std::string const & key, bool recursive = false); + std::shared_ptr rmdir_internal(std::string const & key, std::string const &range_end); + std::shared_ptr ls_internal(std::string const & key, size_t const limit); + std::shared_ptr ls_internal(std::string const & key, std::string const &range_end, size_t const limit); + std::shared_ptr watch_internal(std::string const & key, int64_t fromIndex, bool recursive = false); + std::shared_ptr watch_internal(std::string const & key, std::string const &range_end, int64_t fromIndex); + std::shared_ptr leaserevoke_internal(int64_t lease_id); + std::shared_ptr leasetimetolive_internal(int64_t lease_id); + Response lock_internal(std::string const &key, std::shared_ptr const &keepalive); + std::shared_ptr lock_with_lease_internal(std::string const &key, int64_t lease_id); + std::shared_ptr unlock_internal(std::string const &lock_key); + std::shared_ptr txn_internal(etcdv3::Transaction const &txn); + std::shared_ptr campaign_internal(std::string const &name, int64_t lease_id, std::string const &value); + std::shared_ptr proclaim_internal( + std::string const &name, int64_t lease_id, + std::string const &key, int64_t revision, std::string const &value); + std::shared_ptr leader_internal(std::string const &name); + std::shared_ptr resign_internal(std::string const &name, int64_t lease_id, + std::string const &key, int64_t revision); + + public: + /** + * Return current auth token. + */ + const std::string ¤t_auth_token() const; + + /** + * Obtain the underlying gRPC channel. + */ +#if defined(WITH_GRPC_CHANNEL_CLASS) + std::shared_ptr grpc_channel() const; +#else + std::shared_ptr grpc_channel() const; +#endif + + private: +#if defined(WITH_GRPC_CHANNEL_CLASS) + std::shared_ptr channel; +#else + std::shared_ptr channel; +#endif + + mutable std::unique_ptr token_authenticator; + + struct EtcdServerStubs; + struct EtcdServerStubsDeleter { + void operator()(EtcdServerStubs *stubs); + }; + std::unique_ptr stubs; + + std::mutex mutex_for_keepalives; + std::map leases_for_locks; + std::map> keep_alive_for_locks; + + friend class KeepAlive; + friend class Watcher; + friend class Client; +}; + } #endif diff --git a/etcd/Value.hpp b/etcd/Value.hpp index fc87e50..c3befef 100644 --- a/etcd/Value.hpp +++ b/etcd/Value.hpp @@ -22,6 +22,8 @@ namespace etcd class Value; class Event; class Response; + class Client; + class SyncClient; /** * Represents a value object received from the etcd server @@ -68,6 +70,8 @@ namespace etcd int64_t lease() const; protected: + friend class Client; + friend class SyncClient; friend class Response; friend class BaseResponse; //deliberately done since Value class will be removed during full V3 friend class DeleteRpcResponse; @@ -81,9 +85,9 @@ namespace etcd std::string _key; bool dir; std::string value; - int64_t created; - int64_t modified; - int64_t _version; + int64_t created; + int64_t modified; + int64_t _version; int _ttl; int64_t leaseId; }; diff --git a/etcd/Watcher.hpp b/etcd/Watcher.hpp index 5c558c3..afc6f5f 100644 --- a/etcd/Watcher.hpp +++ b/etcd/Watcher.hpp @@ -6,24 +6,37 @@ #include #include -#include "etcd/Client.hpp" +#include "etcd/SyncClient.hpp" #include "etcd/Response.hpp" namespace etcd { + // forward declaration to avoid header/library dependency + class Client; + class Watcher { public: Watcher(Client const &client, std::string const & key, std::function callback, bool recursive=false); + Watcher(SyncClient const &client, std::string const & key, + std::function callback, bool recursive=false); Watcher(Client const &client, std::string const & key, std::string const &range_end, std::function callback); + Watcher(SyncClient const &client, std::string const & key, + std::string const &range_end, + std::function callback); Watcher(Client const &client, std::string const & key, int64_t fromIndex, std::function callback, bool recursive=false); + Watcher(SyncClient const &client, std::string const & key, int64_t fromIndex, + std::function callback, bool recursive=false); Watcher(Client const &client, std::string const & key, std::string const &range_end, int64_t fromIndex, std::function callback); + Watcher(SyncClient const &client, std::string const & key, + std::string const &range_end, int64_t fromIndex, + std::function callback); Watcher(std::string const & address, std::string const & key, std::function callback, bool recursive=false); Watcher(std::string const & address, std::string const & key, diff --git a/etcd/v3/Action.hpp b/etcd/v3/Action.hpp index 9c99e41..168b9a2 100644 --- a/etcd/v3/Action.hpp +++ b/etcd/v3/Action.hpp @@ -2,6 +2,7 @@ #define __V3_ACTION_HPP__ #include +#include #include #include "proto/rpc.grpc.pb.h" @@ -20,7 +21,7 @@ using v3electionpb::Election; namespace etcdv3 { - enum AtomicityType + enum class AtomicityType { PREV_INDEX = 0, PREV_VALUE = 1 @@ -46,12 +47,15 @@ namespace etcdv3 Lease::Stub* lease_stub; Lock::Stub* lock_stub; Election::Stub* election_stub; + + void dump(std::ostream &os) const; }; class Action { public: Action(etcdv3::ActionParameters const ¶ms); + Action(etcdv3::ActionParameters && params); void waitForResponse(); const std::chrono::high_resolution_clock::time_point startTimepoint(); protected: @@ -60,6 +64,9 @@ namespace etcdv3 CompletionQueue cq_; etcdv3::ActionParameters parameters; std::chrono::high_resolution_clock::time_point start_timepoint; + private: + // Init things like auth token, etc. + void InitAction(); }; namespace detail { diff --git a/etcd/v3/AsyncCompareAndDeleteAction.hpp b/etcd/v3/AsyncCompareAndDeleteAction.hpp index 9dd57c1..2ce8ab9 100644 --- a/etcd/v3/AsyncCompareAndDeleteAction.hpp +++ b/etcd/v3/AsyncCompareAndDeleteAction.hpp @@ -16,7 +16,7 @@ namespace etcdv3 class AsyncCompareAndDeleteAction : public etcdv3::Action { public: - AsyncCompareAndDeleteAction(etcdv3::ActionParameters const ¶m, etcdv3::AtomicityType type); + AsyncCompareAndDeleteAction(etcdv3::ActionParameters && params, etcdv3::AtomicityType type); AsyncTxnResponse ParseResponse(); private: TxnResponse reply; diff --git a/etcd/v3/AsyncCompareAndSwapAction.hpp b/etcd/v3/AsyncCompareAndSwapAction.hpp index a010a29..2cf2aca 100644 --- a/etcd/v3/AsyncCompareAndSwapAction.hpp +++ b/etcd/v3/AsyncCompareAndSwapAction.hpp @@ -16,7 +16,7 @@ namespace etcdv3 class AsyncCompareAndSwapAction : public etcdv3::Action { public: - AsyncCompareAndSwapAction(etcdv3::ActionParameters const ¶m, etcdv3::AtomicityType type); + AsyncCompareAndSwapAction(etcdv3::ActionParameters && params, etcdv3::AtomicityType type); AsyncTxnResponse ParseResponse(); private: TxnResponse reply; diff --git a/etcd/v3/AsyncDeleteAction.hpp b/etcd/v3/AsyncDeleteAction.hpp index 8cb105d..67e3f83 100644 --- a/etcd/v3/AsyncDeleteAction.hpp +++ b/etcd/v3/AsyncDeleteAction.hpp @@ -15,7 +15,7 @@ namespace etcdv3 class AsyncDeleteAction : public etcdv3::Action { public: - AsyncDeleteAction(etcdv3::ActionParameters const ¶m); + AsyncDeleteAction(etcdv3::ActionParameters && params); AsyncDeleteResponse ParseResponse(); private: DeleteRangeResponse reply; diff --git a/etcd/v3/AsyncElectionAction.hpp b/etcd/v3/AsyncElectionAction.hpp index a9f2b47..ca25eb6 100644 --- a/etcd/v3/AsyncElectionAction.hpp +++ b/etcd/v3/AsyncElectionAction.hpp @@ -24,7 +24,7 @@ namespace etcdv3 class AsyncCampaignAction : public etcdv3::Action { public: - AsyncCampaignAction(etcdv3::ActionParameters const ¶m); + AsyncCampaignAction(etcdv3::ActionParameters && params); AsyncCampaignResponse ParseResponse(); private: CampaignResponse reply; @@ -34,7 +34,7 @@ namespace etcdv3 class AsyncProclaimAction : public etcdv3::Action { public: - AsyncProclaimAction(etcdv3::ActionParameters const ¶m); + AsyncProclaimAction(etcdv3::ActionParameters && params); AsyncProclaimResponse ParseResponse(); private: ProclaimResponse reply; @@ -44,7 +44,7 @@ namespace etcdv3 class AsyncLeaderAction : public etcdv3::Action { public: - AsyncLeaderAction(etcdv3::ActionParameters const ¶m); + AsyncLeaderAction(etcdv3::ActionParameters && params); AsyncLeaderResponse ParseResponse(); private: LeaderResponse reply; @@ -54,14 +54,12 @@ namespace etcdv3 class AsyncObserveAction : public etcdv3::Action { public: - AsyncObserveAction(etcdv3::ActionParameters const ¶m, const bool once=false); + AsyncObserveAction(etcdv3::ActionParameters && params); AsyncObserveResponse ParseResponse(); void waitForResponse(); - void waitForResponse(std::function callback); void CancelObserve(); bool Cancelled() const; private: - bool once; LeaderResponse reply; std::unique_ptr> response_reader; std::atomic_bool isCancelled; @@ -71,7 +69,7 @@ namespace etcdv3 class AsyncResignAction : public etcdv3::Action { public: - AsyncResignAction(etcdv3::ActionParameters const ¶m); + AsyncResignAction(etcdv3::ActionParameters && params); AsyncResignResponse ParseResponse(); private: ResignResponse reply; diff --git a/etcd/v3/AsyncHeadAction.hpp b/etcd/v3/AsyncHeadAction.hpp index 6f997b3..7f3a9a8 100644 --- a/etcd/v3/AsyncHeadAction.hpp +++ b/etcd/v3/AsyncHeadAction.hpp @@ -15,7 +15,7 @@ namespace etcdv3 class AsyncHeadAction : public etcdv3::Action { public: - AsyncHeadAction(etcdv3::ActionParameters const ¶m); + AsyncHeadAction(etcdv3::ActionParameters && params); AsyncHeadResponse ParseResponse(); private: RangeResponse reply; diff --git a/etcd/v3/AsyncLeaseAction.hpp b/etcd/v3/AsyncLeaseAction.hpp index 2ae8466..070fa03 100644 --- a/etcd/v3/AsyncLeaseAction.hpp +++ b/etcd/v3/AsyncLeaseAction.hpp @@ -25,7 +25,7 @@ namespace etcdv3 { class AsyncLeaseGrantAction : public etcdv3::Action { public: - AsyncLeaseGrantAction(etcdv3::ActionParameters const ¶m); + AsyncLeaseGrantAction(etcdv3::ActionParameters && params); AsyncLeaseGrantResponse ParseResponse(); private: LeaseGrantResponse reply; @@ -34,7 +34,7 @@ namespace etcdv3 class AsyncLeaseRevokeAction: public etcdv3::Action { public: - AsyncLeaseRevokeAction(etcdv3::ActionParameters const ¶m); + AsyncLeaseRevokeAction(etcdv3::ActionParameters && params); AsyncLeaseRevokeResponse ParseResponse(); private: LeaseRevokeResponse reply; @@ -43,7 +43,7 @@ namespace etcdv3 class AsyncLeaseKeepAliveAction: public etcdv3::Action { public: - AsyncLeaseKeepAliveAction(etcdv3::ActionParameters const ¶m); + AsyncLeaseKeepAliveAction(etcdv3::ActionParameters && params); AsyncLeaseKeepAliveResponse ParseResponse(); etcd::Response Refresh(); @@ -61,7 +61,7 @@ namespace etcdv3 class AsyncLeaseTimeToLiveAction: public etcdv3::Action { public: - AsyncLeaseTimeToLiveAction(etcdv3::ActionParameters const ¶m); + AsyncLeaseTimeToLiveAction(etcdv3::ActionParameters && params); AsyncLeaseTimeToLiveResponse ParseResponse(); private: LeaseTimeToLiveResponse reply; @@ -70,7 +70,7 @@ namespace etcdv3 class AsyncLeaseLeasesAction: public etcdv3::Action { public: - AsyncLeaseLeasesAction(etcdv3::ActionParameters const ¶m); + AsyncLeaseLeasesAction(etcdv3::ActionParameters && params); AsyncLeaseLeasesResponse ParseResponse(); private: LeaseLeasesResponse reply; diff --git a/etcd/v3/AsyncLockAction.hpp b/etcd/v3/AsyncLockAction.hpp index ec8951f..ea0bbcd 100644 --- a/etcd/v3/AsyncLockAction.hpp +++ b/etcd/v3/AsyncLockAction.hpp @@ -19,7 +19,7 @@ namespace etcdv3 class AsyncLockAction : public etcdv3::Action { public: - AsyncLockAction(etcdv3::ActionParameters const ¶m); + AsyncLockAction(etcdv3::ActionParameters && params); AsyncLockResponse ParseResponse(); private: LockResponse reply; @@ -29,7 +29,7 @@ namespace etcdv3 class AsyncUnlockAction : public etcdv3::Action { public: - AsyncUnlockAction(etcdv3::ActionParameters const ¶m); + AsyncUnlockAction(etcdv3::ActionParameters && params); AsyncUnlockResponse ParseResponse(); private: UnlockResponse reply; diff --git a/etcd/v3/AsyncPutAction.hpp b/etcd/v3/AsyncPutAction.hpp index 08c6c4d..f13cd77 100644 --- a/etcd/v3/AsyncPutAction.hpp +++ b/etcd/v3/AsyncPutAction.hpp @@ -15,7 +15,7 @@ namespace etcdv3 class AsyncPutAction : public etcdv3::Action { public: - AsyncPutAction(etcdv3::ActionParameters const ¶m); + AsyncPutAction(etcdv3::ActionParameters && params); AsyncPutResponse ParseResponse(); private: PutResponse reply; diff --git a/etcd/v3/AsyncRangeAction.hpp b/etcd/v3/AsyncRangeAction.hpp index 81d1c62..e082c3a 100644 --- a/etcd/v3/AsyncRangeAction.hpp +++ b/etcd/v3/AsyncRangeAction.hpp @@ -15,7 +15,7 @@ namespace etcdv3 class AsyncRangeAction : public etcdv3::Action { public: - AsyncRangeAction(etcdv3::ActionParameters const ¶m); + AsyncRangeAction(etcdv3::ActionParameters && params); AsyncRangeResponse ParseResponse(); private: RangeResponse reply; diff --git a/etcd/v3/AsyncSetAction.hpp b/etcd/v3/AsyncSetAction.hpp index ce072c7..0c0990c 100644 --- a/etcd/v3/AsyncSetAction.hpp +++ b/etcd/v3/AsyncSetAction.hpp @@ -16,7 +16,7 @@ namespace etcdv3 class AsyncSetAction : public etcdv3::Action { public: - AsyncSetAction(etcdv3::ActionParameters const ¶m, bool create=false); + AsyncSetAction(etcdv3::ActionParameters && params, bool create=false); AsyncTxnResponse ParseResponse(); private: TxnResponse reply; diff --git a/etcd/v3/AsyncTxnAction.hpp b/etcd/v3/AsyncTxnAction.hpp index b5609be..1880842 100644 --- a/etcd/v3/AsyncTxnAction.hpp +++ b/etcd/v3/AsyncTxnAction.hpp @@ -18,7 +18,7 @@ namespace etcdv3 class AsyncTxnAction : public etcdv3::Action { public: - AsyncTxnAction(etcdv3::ActionParameters const ¶m, etcdv3::Transaction const &tx); + AsyncTxnAction(etcdv3::ActionParameters && params, etcdv3::Transaction const &tx); AsyncTxnResponse ParseResponse(); private: TxnResponse reply; diff --git a/etcd/v3/AsyncUpdateAction.hpp b/etcd/v3/AsyncUpdateAction.hpp index 550e313..c57eb87 100644 --- a/etcd/v3/AsyncUpdateAction.hpp +++ b/etcd/v3/AsyncUpdateAction.hpp @@ -16,7 +16,7 @@ namespace etcdv3 class AsyncUpdateAction : public etcdv3::Action { public: - AsyncUpdateAction(etcdv3::ActionParameters const ¶m); + AsyncUpdateAction(etcdv3::ActionParameters && params); AsyncTxnResponse ParseResponse(); private: TxnResponse reply; diff --git a/etcd/v3/AsyncWatchAction.hpp b/etcd/v3/AsyncWatchAction.hpp index cd18489..3ab0ad2 100644 --- a/etcd/v3/AsyncWatchAction.hpp +++ b/etcd/v3/AsyncWatchAction.hpp @@ -19,7 +19,7 @@ namespace etcdv3 class AsyncWatchAction : public etcdv3::Action { public: - AsyncWatchAction(etcdv3::ActionParameters const ¶m); + AsyncWatchAction(etcdv3::ActionParameters && params); AsyncWatchResponse ParseResponse(); void waitForResponse(); void waitForResponse(std::function callback); diff --git a/etcd/v3/Transaction.hpp b/etcd/v3/Transaction.hpp index bdf3871..fba4085 100644 --- a/etcd/v3/Transaction.hpp +++ b/etcd/v3/Transaction.hpp @@ -50,7 +50,7 @@ public: void setup_put(std::string const &key, std::string const &value); void setup_delete(std::string const &key); - std::unique_ptr txn_request; + std::shared_ptr txn_request; private: std::string key; }; diff --git a/etcd/v3/action_constants.hpp b/etcd/v3/action_constants.hpp index fa8cb5f..8c4d29a 100644 --- a/etcd/v3/action_constants.hpp +++ b/etcd/v3/action_constants.hpp @@ -44,6 +44,7 @@ namespace etcdv3 extern const int ERROR_KEY_NOT_FOUND; extern const int ERROR_COMPARE_FAILED; extern const int ERROR_KEY_ALREADY_EXISTS; + extern const int ERROR_ACTION_CANCELLED; } #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 65f021d..eeb852e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,30 +1,56 @@ -file(GLOB_RECURSE CPP_CLIENT_SRC - RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" - "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/**/*.cpp") - +# grpc stuffs set_source_files_properties(${PROTOBUF_GENERATES} PROPERTIES GENERATED TRUE) -add_library(etcd-cpp-api ${CPP_CLIENT_SRC} ${PROTOBUF_GENERATES}) -add_dependencies(etcd-cpp-api protobuf_generates) -target_link_libraries(etcd-cpp-api PUBLIC +macro(include_generated_protobuf_files target) + target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen) + target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen/proto) +endmacro() + +# prepare common objects +file(GLOB_RECURSE CPP_CLIENT_CORE_SRC + RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/KeepAlive.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Response.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/SyncClient.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Value.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Watcher.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/**/*.cpp" +) + +add_library(etcd-cpp-api-core-objects OBJECT ${CPP_CLIENT_CORE_SRC} ${PROTOBUF_GENERATES}) +add_dependencies(etcd-cpp-api-core-objects protobuf_generates) +include_generated_protobuf_files(etcd-cpp-api-core-objects) + +# add the core library, includes the sycnhronous client only +add_library(etcd-cpp-api-core $) +target_link_libraries(etcd-cpp-api-core PUBLIC ${Boost_LIBRARIES} - ${CPPREST_LIB} ${PROTOBUF_LIBRARIES} ${OPENSSL_LIBRARIES} - ${GRPC_LIBRARIES}) + ${GRPC_LIBRARIES} +) +include_generated_protobuf_files(etcd-cpp-api-core) -target_include_directories(etcd-cpp-api PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen) -target_include_directories(etcd-cpp-api PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen/proto) +# add the client with asynchronus client +add_library(etcd-cpp-api $ + "${CMAKE_CURRENT_SOURCE_DIR}/Client.cpp") +target_link_libraries(etcd-cpp-api PUBLIC + ${Boost_LIBRARIES} + ${CPPREST_LIB} # n.b.: the asynchronous client requires pplx in cpprestsdk + ${PROTOBUF_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${GRPC_LIBRARIES} +) +include_generated_protobuf_files(etcd-cpp-api) if("${CMAKE_VERSION}" VERSION_LESS "3.14") - install(TARGETS etcd-cpp-api + install(TARGETS etcd-cpp-api-core etcd-cpp-api EXPORT etcd-targets RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) else() - install(TARGETS etcd-cpp-api + install(TARGETS etcd-cpp-api-core etcd-cpp-api EXPORT etcd-targets) endif() diff --git a/src/Client.cpp b/src/Client.cpp index 2dae73f..fa19412 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -1,6 +1,9 @@ +#include #include #include #include +#include "etcd/SyncClient.hpp" +#include "etcd/Value.hpp" #if defined(_WIN32) #include @@ -18,6 +21,7 @@ #include #include #include +#include #include @@ -29,6 +33,7 @@ #include "etcd/Client.hpp" #include "etcd/KeepAlive.hpp" +#include "etcd/Watcher.hpp" #include "etcd/v3/action_constants.hpp" #include "etcd/v3/Action.hpp" #include "etcd/v3/AsyncRangeResponse.hpp" @@ -54,223 +59,34 @@ #include "etcd/v3/AsyncElectionAction.hpp" #include "etcd/v3/AsyncTxnAction.hpp" -using grpc::Channel; - -namespace etcd { -namespace detail { - -static bool dns_resolve(std::string const &target, std::vector &endpoints) { - struct addrinfo hints = {}, *addrs; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - std::vector target_parts; - boost::split(target_parts, target, boost::is_any_of(":")); - if (target_parts.size() != 2) { - std::cerr << "warn: invalid URL: " << target << std::endl; - return false; - } - -#if defined(_WIN32) - { - // Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h. - WORD wVersionRequested = MAKEWORD(2, 2); - WSADATA wsaData; - - int err = WSAStartup(wVersionRequested, &wsaData); - if (err != 0) { - // Tell the user that we could not find a usable Winsock DLL. - std::cerr << "WSAStartup failed with error: %d" << err << std::endl; - return false; - } - } -#endif - - int r = getaddrinfo(target_parts[0].c_str(), target_parts[1].c_str(), &hints, &addrs); - if (r != 0) { - std::cerr << "warn: getaddrinfo() failed for endpoint " << target - << " with error: " << r << std::endl; - return false; - } - - char host[16] = {'\0'}; - for (struct addrinfo* addr = addrs; addr != nullptr; addr = addr->ai_next) { - memset(host, '\0', sizeof(host)); - getnameinfo(addr->ai_addr, addr->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST); - endpoints.emplace_back(std::string(host) + ":" + target_parts[1]); - } - freeaddrinfo(addrs); - return true; +etcd::Client::Client(etcd::SyncClient *client): client(client) +{ + this->own_client = false; } -const std::string strip_and_resolve_addresses(std::string const &address) { - std::vector addresses; - boost::algorithm::split(addresses, address, boost::algorithm::is_any_of(",;")); - std::string stripped_address; - { - std::vector stripped_addresses; - std::string substr("://"); - for (auto const &addr: addresses) { - std::string::size_type idx = addr.find(substr); - std::string target = idx == std::string::npos ? addr : addr.substr(idx + substr.length()); - etcd::detail::dns_resolve(target, stripped_addresses); - } - stripped_address = boost::algorithm::join(stripped_addresses, ","); - } - return "ipv4:///" + stripped_address; -} - -const bool authenticate(std::shared_ptr const &channel, - std::string const &username, - std::string const &password, - std::string &token_or_message) { - // run a round of auth - auto auth_stub = etcdserverpb::Auth::NewStub(channel); - ClientContext context; - etcdserverpb::AuthenticateRequest auth_request; - etcdserverpb::AuthenticateResponse auth_response; - auth_request.set_name(username); - auth_request.set_password(password); - auto status = auth_stub->Authenticate(&context, auth_request, &auth_response); - if (status.ok()) { - token_or_message = auth_response.token(); - return true; - } else { - token_or_message = status.error_message(); - return false; - } -} - -static std::string read_from_file(std::string const &filename) { - std::ifstream file(filename.c_str(), std::ios::in); - if (file.is_open()) { - std::stringstream ss; - ss << file.rdbuf (); - file.close (); - return ss.str (); - } - return std::string{}; -} - -static grpc::SslCredentialsOptions make_ssl_credentials(std::string const &ca, - std::string const &cert, - std::string const &key) { - grpc::SslCredentialsOptions options; - options.pem_root_certs = read_from_file(ca); - options.pem_cert_chain = read_from_file(cert); - options.pem_private_key = read_from_file(key); - return options; -} - -} -} - -class etcd::Client::TokenAuthenticator { - private: - std::shared_ptr channel_; - std::string username_, password_, token_; - int ttl_ = 300; // see also --auth-token-ttl for etcd - std::chrono::time_point updated_at; - std::mutex mtx_; - bool has_token_ = false; - - public: - TokenAuthenticator(): has_token_(false) { - } - - TokenAuthenticator(std::shared_ptr channel, - std::string const &username, - std::string const &password, - const int ttl=300) - : channel_(channel), username_(username), password_(password), ttl_(ttl), has_token_(false) { - if ((!username.empty()) && (!(password.empty()))) { - has_token_ = true; - renew_if_expired(true); - } - } - - std::string const &renew_if_expired(const bool force = false) { - if (!has_token_) { - return token_; - } - std::lock_guard scoped_lock(mtx_); - if (force || (!token_.empty())) { - auto tp = std::chrono::system_clock::now(); - if (force || std::chrono::duration_cast(tp - updated_at).count() - > std::max(1, ttl_ - 3)) { - updated_at = tp; - // auth - if (!etcd::detail::authenticate(this->channel_, username_, password_, token_)) { - throw std::invalid_argument("Etcd authentication failed: " + token_); - } - } - } - return token_; - } -}; - -void etcd::Client::TokenAuthenticatorDeleter::operator()(etcd::Client::TokenAuthenticator *authenticator) { - if (authenticator) { - delete authenticator; - } -} - -struct etcd::Client::EtcdServerStubs { - std::unique_ptr kvServiceStub; - std::unique_ptr watchServiceStub; - std::unique_ptr leaseServiceStub; - std::unique_ptr lockServiceStub; - std::unique_ptr electionServiceStub; -}; - -void etcd::Client::EtcdServerStubsDeleter::operator()(etcd::Client::EtcdServerStubs *stubs) { - if (stubs) { - delete stubs; - } +etcd::Client* WithClient(etcd::SyncClient *client) +{ + return new etcd::Client(client); } etcd::Client::Client(std::string const & address, std::string const & load_balancer) { - // create channels - std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); - grpc::ChannelArguments grpc_args; - grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); - grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); - std::shared_ptr creds = grpc::InsecureChannelCredentials(); - grpc_args.SetLoadBalancingPolicyName(load_balancer); - this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); - this->token_authenticator.reset(new TokenAuthenticator()); - - // create stubs - stubs.reset(new EtcdServerStubs{}); - stubs->kvServiceStub = KV::NewStub(this->channel); - stubs->watchServiceStub= Watch::NewStub(this->channel); - stubs->leaseServiceStub= Lease::NewStub(this->channel); - stubs->lockServiceStub = Lock::NewStub(this->channel); - stubs->electionServiceStub = Election::NewStub(this->channel); + this->own_client = true; + this->client = new SyncClient(address, load_balancer); } etcd::Client::Client(std::string const & address, - grpc::ChannelArguments const & arguments) -{ - // create channels - std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); - grpc::ChannelArguments grpc_args = arguments; - grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); - grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); - std::shared_ptr creds = grpc::InsecureChannelCredentials(); - this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); - this->token_authenticator.reset(new TokenAuthenticator()); +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif - // create stubs - stubs.reset(new EtcdServerStubs{}); - stubs->kvServiceStub = KV::NewStub(this->channel); - stubs->watchServiceStub= Watch::NewStub(this->channel); - stubs->leaseServiceStub= Lease::NewStub(this->channel); - stubs->lockServiceStub = Lock::NewStub(this->channel); - stubs->electionServiceStub = Election::NewStub(this->channel); + ) +{ + this->own_client = true; + this->client = new SyncClient(address, arguments); } etcd::Client *etcd::Client::WithUrl(std::string const & etcd_url, @@ -279,7 +95,13 @@ etcd::Client *etcd::Client::WithUrl(std::string const & etcd_url, } etcd::Client *etcd::Client::WithUrl(std::string const & etcd_url, - grpc::ChannelArguments const & arguments) { +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + + ) { return new etcd::Client(etcd_url, arguments); } @@ -289,51 +111,24 @@ etcd::Client::Client(std::string const & address, int const auth_token_ttl, std::string const & load_balancer) { - // create channels - std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); - grpc::ChannelArguments grpc_args; - grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); - grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); - std::shared_ptr creds = grpc::InsecureChannelCredentials(); - grpc_args.SetLoadBalancingPolicyName(load_balancer); - this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); - - // auth - this->token_authenticator.reset(new TokenAuthenticator(this->channel, username, password, auth_token_ttl)); - - // setup stubs - stubs.reset(new EtcdServerStubs{}); - stubs->kvServiceStub = KV::NewStub(this->channel); - stubs->watchServiceStub= Watch::NewStub(this->channel); - stubs->leaseServiceStub= Lease::NewStub(this->channel); - stubs->lockServiceStub = Lock::NewStub(this->channel); - stubs->electionServiceStub = Election::NewStub(this->channel); + this->own_client = true; + this->client = new SyncClient(address, username, password, auth_token_ttl, load_balancer); } etcd::Client::Client(std::string const & address, std::string const & username, std::string const & password, int const auth_token_ttl, - grpc::ChannelArguments const & arguments) +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + + ) { - // create channels - std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); - grpc::ChannelArguments grpc_args = arguments; - grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); - grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); - std::shared_ptr creds = grpc::InsecureChannelCredentials(); - this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); - - // auth - this->token_authenticator.reset(new TokenAuthenticator(this->channel, username, password, auth_token_ttl)); - - // setup stubs - stubs.reset(new EtcdServerStubs{}); - stubs->kvServiceStub = KV::NewStub(this->channel); - stubs->watchServiceStub= Watch::NewStub(this->channel); - stubs->leaseServiceStub= Lease::NewStub(this->channel); - stubs->lockServiceStub = Lock::NewStub(this->channel); - stubs->electionServiceStub = Election::NewStub(this->channel); + this->own_client = true; + this->client = new SyncClient(address, username, password, auth_token_ttl, arguments); } etcd::Client *etcd::Client::WithUser(std::string const & etcd_url, @@ -348,7 +143,13 @@ etcd::Client *etcd::Client::WithUser(std::string const & etcd_url, std::string const & username, std::string const & password, int const auth_token_ttl, - grpc::ChannelArguments const & arguments) { +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + + ) { return new etcd::Client(etcd_url, username, password, auth_token_ttl, arguments); } @@ -360,27 +161,8 @@ etcd::Client::Client(std::string const & address, std::string const & target_name_override, std::string const & load_balancer) { - // create channels - std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); - grpc::ChannelArguments grpc_args; - grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); - grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); - std::shared_ptr creds = grpc::SslCredentials( - etcd::detail::make_ssl_credentials(ca, cert, key)); - grpc_args.SetLoadBalancingPolicyName(load_balancer); - if (!target_name_override.empty()) { - grpc_args.SetString(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, target_name_override); - } - this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); - this->token_authenticator.reset(new TokenAuthenticator()); - - // setup stubs - stubs.reset(new EtcdServerStubs{}); - stubs->kvServiceStub = KV::NewStub(this->channel); - stubs->watchServiceStub= Watch::NewStub(this->channel); - stubs->leaseServiceStub= Lease::NewStub(this->channel); - stubs->lockServiceStub = Lock::NewStub(this->channel); - stubs->electionServiceStub = Election::NewStub(this->channel); + this->own_client = true; + this->client = new SyncClient(address, ca, cert, key, target_name_override, load_balancer); } etcd::Client::Client(std::string const & address, @@ -388,28 +170,16 @@ etcd::Client::Client(std::string const & address, std::string const & cert, std::string const & key, std::string const & target_name_override, - grpc::ChannelArguments const & arguments) -{ - // create channels - std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); - grpc::ChannelArguments grpc_args = arguments; - grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); - grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); - std::shared_ptr creds = grpc::SslCredentials( - etcd::detail::make_ssl_credentials(ca, cert, key)); - if (!target_name_override.empty()) { - grpc_args.SetString(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, target_name_override); - } - this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); - this->token_authenticator.reset(new TokenAuthenticator()); +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif - // setup stubs - stubs.reset(new EtcdServerStubs{}); - stubs->kvServiceStub = KV::NewStub(this->channel); - stubs->watchServiceStub= Watch::NewStub(this->channel); - stubs->leaseServiceStub= Lease::NewStub(this->channel); - stubs->lockServiceStub = Lock::NewStub(this->channel); - stubs->electionServiceStub = Election::NewStub(this->channel); + ) +{ + this->own_client = true; + this->client = new SyncClient(address, ca, cert, key, target_name_override, arguments); } etcd::Client *etcd::Client::WithSSL(std::string const & etcd_url, @@ -422,7 +192,11 @@ etcd::Client *etcd::Client::WithSSL(std::string const & etcd_url, } etcd::Client *etcd::Client::WithSSL(std::string const & etcd_url, - grpc::ChannelArguments const & arguments, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments, +#else + grpc_impl::ChannelArguments const & arguments, +#endif std::string const & ca, std::string const & cert, std::string const & key, @@ -430,629 +204,458 @@ etcd::Client *etcd::Client::WithSSL(std::string const & etcd_url, return new etcd::Client(etcd_url, ca, cert, key, target_name_override, arguments); } +etcd::Client::~Client() { + if (this->own_client) { + delete this->client; + } +} + + +namespace etcd { +namespace detail { + +// Pass rvalue reference to lambda, +// +// Inspired by: https://stackoverflow.com/a/20669290/5080177 + +template +class capture_impl +{ + mutable A a; + mutable F f; +public: + capture_impl(A && a, F && f) + : a{std::forward(a)}, f{std::forward(f)} + {} + + template auto operator()( Ts&&...args ) + -> decltype(std::forward(f)(std::forward(a), std::forward(args)... )) + { + return std::forward(f)(std::forward(a), std::forward(args)... ); + } + + template auto operator()( Ts&&...args ) const + -> decltype(std::forward(f)(std::forward(a), std::forward(args)... )) + { + return std::forward(f)(std::forward(a), std::forward(args)... ); + } +}; + +template +static capture_impl capture(A && a, F && f) +{ + return capture_impl(std::forward(a), std::forward(f)); +} + +template +static auto asyncify(F && fn, Params && params, Ts... args) -> pplx::task(params), std::bind(std::forward(fn), std::placeholders::_1, std::forward(args)...))())> { + return pplx::task(params), std::bind(std::forward(fn), std::placeholders::_1, std::forward(args)...))())>(capture(std::forward(params), std::bind(std::forward(fn), std::placeholders::_1, std::forward(args)...))); +} + +} // namespace detail + +template +using responser_t = etcd::Response (*)(std::shared_ptr); + +} // namespace etcd + pplx::task etcd::Client::head() { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncHeadAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->head_internal()); } pplx::task etcd::Client::get(std::string const & key) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.withPrefix = false; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncRangeAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->get_internal(key)); } pplx::task etcd::Client::set(std::string const & key, std::string const & value, int ttl) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.value.assign(value); - params.kv_stub = stubs->kvServiceStub.get(); - - if(ttl > 0) - { - auto res = leasegrant(ttl).get(); - if(!res.is_ok()) - { - return pplx::task([res]() - { - return etcd::Response(res.error_code(), res.error_message().c_str()); - }); - } - else - { - params.lease_id = res.value().lease(); - } + if (ttl > 0) { + return this->leasegrant(ttl).then([this, key, value](pplx::task const &task) { + auto resp = task.get(); + if (resp.error_code() == 0) { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->set_internal(key, value, resp.value().lease())); + } else { + return pplx::task_from_result(resp); + } + }); + } else { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->set_internal(key, value, 0)); } - - std::shared_ptr call(new etcdv3::AsyncSetAction(params)); - return Response::create(call); } pplx::task etcd::Client::set(std::string const & key, std::string const & value, int64_t leaseid) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.value.assign(value); - params.lease_id = leaseid; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncSetAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->set_internal(key, value, leaseid)); } - pplx::task etcd::Client::add(std::string const & key, std::string const & value, int ttl) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.value.assign(value); - params.kv_stub = stubs->kvServiceStub.get(); - - if(ttl > 0) - { - auto res = leasegrant(ttl).get(); - if(!res.is_ok()) - { - return pplx::task([res]() - { - return etcd::Response(res.error_code(), res.error_message().c_str()); - }); - } - else - { - params.lease_id = res.value().lease(); - } + if (ttl > 0) { + return this->leasegrant(ttl).then([this, key, value](pplx::task const &task) { + auto resp = task.get(); + if (resp.error_code() == 0) { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->add_internal(key, value, resp.value().lease())); + } else { + return pplx::task_from_result(resp); + } + }); + } else { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->add_internal(key, value, 0)); } - std::shared_ptr call(new etcdv3::AsyncSetAction(params, true)); - return Response::create(call); } pplx::task etcd::Client::add(std::string const & key, std::string const & value, int64_t leaseid) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.value.assign(value); - params.lease_id = leaseid; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncSetAction(params,true)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->add_internal(key, value, leaseid)); } pplx::task etcd::Client::put(std::string const & key, std::string const & value) { - etcdv3::ActionParameters params; - params.key.assign(key); - params.value.assign(value); - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncPutAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->put_internal(key, value)); } pplx::task etcd::Client::modify(std::string const & key, std::string const & value, int ttl) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.value.assign(value); - params.kv_stub = stubs->kvServiceStub.get(); - - if(ttl > 0) - { - auto res = leasegrant(ttl).get(); - if(!res.is_ok()) - { - return pplx::task([res]() - { - return etcd::Response(res.error_code(), res.error_message().c_str()); - }); - } - else - { - params.lease_id = res.value().lease(); - } + if (ttl > 0) { + return this->leasegrant(ttl).then([this, key, value](pplx::task const &task) { + auto resp = task.get(); + if (resp.error_code() == 0) { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->modify_internal(key, value, resp.value().lease())); + } else { + return pplx::task_from_result(resp); + } + }); + } else { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->modify_internal(key, value, 0)); } - std::shared_ptr call(new etcdv3::AsyncUpdateAction(params)); - return Response::create(call); } pplx::task etcd::Client::modify(std::string const & key, std::string const & value, int64_t leaseid) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.value.assign(value); - params.lease_id = leaseid; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncUpdateAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->modify_internal(key, value, leaseid)); } - pplx::task etcd::Client::modify_if(std::string const & key, std::string const & value, std::string const & old_value, int ttl) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.value.assign(value); - params.old_value.assign(old_value); - params.kv_stub = stubs->kvServiceStub.get(); - - if(ttl > 0) - { - auto res = leasegrant(ttl).get(); - if(!res.is_ok()) - { - return pplx::task([res]() - { - return etcd::Response(res.error_code(), res.error_message().c_str()); - }); - } - else - { - params.lease_id = res.value().lease(); - } + if (ttl > 0) { + return this->leasegrant(ttl).then([this, key, value, old_value](pplx::task const &task) { + auto resp = task.get(); + if (resp.error_code() == 0) { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->modify_if_internal(key, value, 0, old_value, resp.value().lease(), etcdv3::AtomicityType::PREV_VALUE)); + } else { + return pplx::task_from_result(resp); + } + }); + } else { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->modify_if_internal(key, value, 0, old_value, 0, etcdv3::AtomicityType::PREV_VALUE)); } - std::shared_ptr call( - new etcdv3::AsyncCompareAndSwapAction(params,etcdv3::AtomicityType::PREV_VALUE)); - return Response::create(call); } pplx::task etcd::Client::modify_if(std::string const & key, std::string const & value, std::string const & old_value, int64_t leaseid) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.value.assign(value); - params.old_value.assign(old_value); - params.lease_id = leaseid; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call( - new etcdv3::AsyncCompareAndSwapAction(params,etcdv3::AtomicityType::PREV_VALUE)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->modify_if_internal(key, value, 0, old_value, leaseid, etcdv3::AtomicityType::PREV_VALUE)); } pplx::task etcd::Client::modify_if(std::string const & key, std::string const & value, int64_t old_index, int ttl) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.value.assign(value); - params.old_revision = old_index; - params.kv_stub = stubs->kvServiceStub.get(); - if(ttl > 0) - { - auto res = leasegrant(ttl).get(); - if(!res.is_ok()) - { - return pplx::task([res]() - { - return etcd::Response(res.error_code(), res.error_message().c_str()); - }); - } - else - { - params.lease_id = res.value().lease(); - } + if (ttl > 0) { + return this->leasegrant(ttl).then([this, key, value, old_index](pplx::task const &task) { + auto resp = task.get(); + if (resp.error_code() == 0) { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->modify_if_internal(key, value, old_index, "", resp.value().lease(), etcdv3::AtomicityType::PREV_INDEX)); + } else { + return pplx::task_from_result(resp); + } + }); + } else { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->modify_if_internal(key, value, old_index, "", 0, etcdv3::AtomicityType::PREV_INDEX)); } - std::shared_ptr call( - new etcdv3::AsyncCompareAndSwapAction(params,etcdv3::AtomicityType::PREV_INDEX)); - return Response::create(call); } pplx::task etcd::Client::modify_if(std::string const & key, std::string const & value, int64_t old_index, int64_t leaseid) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.value.assign(value); - params.lease_id = leaseid; - params.old_revision = old_index; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call( - new etcdv3::AsyncCompareAndSwapAction(params,etcdv3::AtomicityType::PREV_INDEX)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->modify_if_internal(key, value, old_index, "", leaseid, etcdv3::AtomicityType::PREV_INDEX)); } - pplx::task etcd::Client::rm(std::string const & key) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.withPrefix = false; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncDeleteAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->rm_internal(key)); } pplx::task etcd::Client::rm_if(std::string const & key, std::string const & old_value) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.old_value.assign(old_value); - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call( - new etcdv3::AsyncCompareAndDeleteAction(params,etcdv3::AtomicityType::PREV_VALUE)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->rm_if_internal(key, 0, old_value, etcdv3::AtomicityType::PREV_VALUE)); } pplx::task etcd::Client::rm_if(std::string const & key, int64_t old_index) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.old_revision = old_index; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call( - new etcdv3::AsyncCompareAndDeleteAction(params, etcdv3::AtomicityType::PREV_INDEX));; - return Response::create(call); - + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->rm_if_internal(key, old_index, "", etcdv3::AtomicityType::PREV_INDEX)); } pplx::task etcd::Client::rmdir(std::string const & key, bool recursive) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.withPrefix = recursive; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncDeleteAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->rmdir_internal(key, recursive)); } pplx::task etcd::Client::rmdir(std::string const & key, const char *range_end) { - return rmdir(key, std::string(range_end)); + return this->rmdir(key, std::string(range_end)); } pplx::task etcd::Client::rmdir(std::string const & key, std::string const &range_end) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.range_end.assign(range_end); - params.withPrefix = false; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncDeleteAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->rmdir_internal(key, range_end)); } pplx::task etcd::Client::ls(std::string const & key) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.withPrefix = true; - params.limit = 0; // default no limit. - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncRangeAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->ls_internal(key, 0)); } pplx::task etcd::Client::ls(std::string const & key, size_t const limit) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.withPrefix = true; - params.limit = limit; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncRangeAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->ls_internal(key, limit)); } pplx::task etcd::Client::ls(std::string const & key, std::string const &range_end) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.range_end.assign(range_end); - params.withPrefix = false; - params.limit = 0; // default no limit. - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncRangeAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->ls_internal(key, range_end, 0)); } pplx::task etcd::Client::ls(std::string const & key, std::string const &range_end, size_t const limit) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.range_end.assign(range_end); - params.withPrefix = false; - params.limit = limit; - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncRangeAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->ls_internal(key, range_end, limit)); } pplx::task etcd::Client::watch(std::string const & key, bool recursive) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.withPrefix = recursive; - params.watch_stub = stubs->watchServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncWatchAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->watch_internal(key, 0, recursive)); } pplx::task etcd::Client::watch(std::string const & key, int64_t fromIndex, bool recursive) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.withPrefix = recursive; - params.revision = fromIndex; - params.watch_stub = stubs->watchServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncWatchAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->watch_internal(key, fromIndex, recursive)); } pplx::task etcd::Client::watch(std::string const & key, const char *range_end) { - return watch(key, std::string(range_end)); + return this->watch(key, std::string(range_end)); } pplx::task etcd::Client::watch(std::string const & key, std::string const & range_end) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.range_end.assign(range_end); - params.withPrefix = false; - params.watch_stub = stubs->watchServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncWatchAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->watch_internal(key, range_end, 0)); } pplx::task etcd::Client::watch(std::string const & key, std::string const & range_end, int64_t fromIndex) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key.assign(key); - params.range_end.assign(range_end); - params.withPrefix = false; - params.revision = fromIndex; - params.watch_stub = stubs->watchServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncWatchAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->watch_internal(key, range_end, fromIndex)); } pplx::task etcd::Client::leasegrant(int ttl) { - // lease grant is special, that we are expected the callback could be invoked - // immediately after the lease is granted by the server. - return Response::create([this, ttl]() { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.ttl = ttl; - params.lease_stub = stubs->leaseServiceStub.get(); - return std::make_shared(params); + // See Note [lease with TTL and issue the actual request] + return pplx::task([this, ttl]() { + return this->client->leasegrant(ttl); }); } pplx::task> etcd::Client::leasekeepalive(int ttl) { - return pplx::task>([this, ttl]() - { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.ttl = ttl; - params.lease_stub = stubs->leaseServiceStub.get(); - auto call = std::make_shared(params); - - call->waitForResponse(); - auto v3resp = call->ParseResponse(); - return std::make_shared(*this, ttl, v3resp.get_value().kvs.lease()); + // See Note [lease with TTL and issue the actual request] + return pplx::task>([this, ttl]() { + return this->client->leasekeepalive(ttl); }); } pplx::task etcd::Client::leaserevoke(int64_t lease_id) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.lease_id = lease_id; - params.lease_stub = stubs->leaseServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncLeaseRevokeAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->leaserevoke_internal(lease_id)); } pplx::task etcd::Client::leasetimetolive(int64_t lease_id) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.lease_id = lease_id; - params.lease_stub = stubs->leaseServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncLeaseTimeToLiveAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->leasetimetolive_internal(lease_id)); } pplx::task etcd::Client::lock(std::string const &key) { - // routines in lock usually will be fast, less than 10 seconds. - // - // (base on our experiences in vineyard and GraphScope). - static const int DEFAULT_LEASE_TTL_FOR_LOCK = 10; + static const int DEFAULT_LEASE_TTL_FOR_LOCK = 10; // see also etcd::SyncClient::lock return this->lock(key, DEFAULT_LEASE_TTL_FOR_LOCK); } pplx::task etcd::Client::lock(std::string const &key, int lease_ttl) { - return this->leasekeepalive(lease_ttl).then([this, key]( - pplx::task> const& resp_task) { - auto const &keepalive = resp_task.get(); - - int64_t lease_id = keepalive->Lease(); - { - std::lock_guard lexical_scope_lock(mutex_for_keepalives); - this->keep_alive_for_locks[lease_id] = keepalive; - } - - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key = key; - params.lease_id = lease_id; - params.lock_stub = stubs->lockServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncLockAction(params)); - - auto lock_resp = Response::create_sync(call); - { - std::lock_guard lexical_scope_lock(mutex_for_keepalives); - if (lock_resp.is_ok()) { - this->leases_for_locks[lock_resp.lock_key()] = lease_id; - } else { - this->keep_alive_for_locks.erase(lease_id); - } - } - return lock_resp; + // See Note [lease with TTL and issue the actual request] + // See also SyncClient::lock + // + // We don't separate the keepalive step and lock step to avoid leaving many + // busy-waiting keepalive tasks when waiting for the locks due to the contention + // of async threadpool. + return pplx::task([this, key, lease_ttl]() { + return this->client->lock(key, lease_ttl); }); } pplx::task etcd::Client::lock_with_lease(std::string const &key, - int64_t lease_id) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key = key; - params.lease_id = lease_id; - params.lock_stub = stubs->lockServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncLockAction(params)); - return Response::create(call); + int64_t lease_id) { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->lock_with_lease_internal(key, lease_id)); } pplx::task etcd::Client::unlock(std::string const &lock_key) { - // issue a "unlock" request - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.key = lock_key; - params.lock_stub = stubs->lockServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncUnlockAction(params)); - - // cancel the KeepAlive first, if it exists - { - std::lock_guard lexical_scope_lock(mutex_for_keepalives); - auto p_leases = this->leases_for_locks.find(lock_key); - if (p_leases != this->leases_for_locks.end()) { - auto p_keeps_alive = this->keep_alive_for_locks.find(p_leases->second); - if (p_keeps_alive != this->keep_alive_for_locks.end()) { - this->keep_alive_for_locks.erase(p_keeps_alive); - } else { -#if !defined(NDEBUG) - std::cerr << "Keepalive for lease not found" << std::endl; -#endif - } - this->leases_for_locks.erase(p_leases); - } else { -#if !defined(NDEBUG) - std::cerr << "Lease for lock not found" << std::endl; -#endif - } - } - - // wait in the io_context loop. - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->unlock_internal(lock_key)); } pplx::task etcd::Client::txn(etcdv3::Transaction const &txn) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.kv_stub = stubs->kvServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncTxnAction(params, txn)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->txn_internal(txn)); } pplx::task etcd::Client::campaign( std::string const &name, int64_t lease_id, std::string const &value) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.name = name; - params.lease_id = lease_id; - params.value = value; - params.election_stub = stubs->electionServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncCampaignAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->campaign_internal(name, lease_id, value)); } pplx::task etcd::Client::proclaim( std::string const &name, int64_t lease_id, std::string const &key, int64_t revision, std::string const &value) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.name = name; - params.lease_id = lease_id; - params.key = key; - params.revision = revision; - params.value = value; - params.election_stub = stubs->electionServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncProclaimAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->proclaim_internal(name, lease_id, key, revision, value)); } pplx::task etcd::Client::leader(std::string const &name) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.name = name; - params.election_stub = stubs->electionServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncLeaderAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->leader_internal(name)); } -std::unique_ptr etcd::Client::observe( - std::string const &name, const bool once) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.name.assign(name); - params.election_stub = stubs->electionServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncObserveAction(params, once)); - std::unique_ptr observer(new Observer()); - observer->action = call; - observer->resp = Response::create(call); - return observer; -} - -std::unique_ptr etcd::Client::observe( - std::string const &name, std::function callback, const bool once) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.name.assign(name); - params.election_stub = stubs->electionServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncObserveAction(params, once)); - std::unique_ptr observer(new Observer()); - observer->action = call; - observer->resp = Response::create(call, callback); - return observer; +std::unique_ptr etcd::Client::observe(std::string const &name) { + return this->client->observe(name); } pplx::task etcd::Client::resign( std::string const &name, int64_t lease_id, std::string const &key, int64_t revision) { - etcdv3::ActionParameters params; - params.auth_token.assign(this->token_authenticator->renew_if_expired()); - params.name = name; - params.lease_id = lease_id; - params.key = key; - params.revision = revision; - params.election_stub = stubs->electionServiceStub.get(); - std::shared_ptr call(new etcdv3::AsyncResignAction(params)); - return Response::create(call); + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->resign_internal(name, lease_id, key, revision)); } const std::string &etcd::Client::current_auth_token() const { - return this->token_authenticator->renew_if_expired(); + return this->client->current_auth_token(); } -etcd::Client::Observer::~Observer() { - if (action != nullptr) { - action->CancelObserve(); - resp.wait(); - } +std::shared_ptr etcd::Client::grpc_channel() const { + return this->client->grpc_channel(); +} + +etcd::SyncClient* etcd::Client::sync_client() const { + return this->client; +} + +// watchers from the async client + +etcd::Watcher::Watcher(Client const &client, std::string const & key, + std::function callback, bool recursive): + Watcher(*client.sync_client(), key, callback, recursive) { +} + +etcd::Watcher::Watcher(Client const &client, std::string const & key, + std::string const &range_end, + std::function callback): + Watcher(*client.sync_client(), key, range_end, callback) { +} + +etcd::Watcher::Watcher(Client const &client, std::string const & key, int64_t fromIndex, + std::function callback, bool recursive): + Watcher(*client.sync_client(), key, fromIndex, callback, recursive) { +} + +etcd::Watcher::Watcher(Client const &client, std::string const & key, + std::string const &range_end, int64_t fromIndex, + std::function callback): + Watcher(*client.sync_client(), key, range_end, fromIndex, callback) { +} + +// keepalive from then async client + +etcd::KeepAlive::KeepAlive(Client const &client, int ttl, int64_t lease_id): + KeepAlive(*client.sync_client(), ttl, lease_id) { +} + +etcd::KeepAlive::KeepAlive(Client const &client, + std::function const &handler, + int ttl, int64_t lease_id): + KeepAlive(*client.sync_client(), handler, ttl, lease_id) { } diff --git a/src/KeepAlive.cpp b/src/KeepAlive.cpp index c298913..130fe42 100644 --- a/src/KeepAlive.cpp +++ b/src/KeepAlive.cpp @@ -21,10 +21,10 @@ void etcd::KeepAlive::EtcdServerStubsDeleter::operator()(etcd::KeepAlive::EtcdSe } } -etcd::KeepAlive::KeepAlive(Client const &client, int ttl, int64_t lease_id): +etcd::KeepAlive::KeepAlive(SyncClient const &client, int ttl, int64_t lease_id): ttl(ttl), lease_id(lease_id), continue_next(true) { stubs.reset(new EtcdServerStubs{}); - stubs->leaseServiceStub = Lease::NewStub(client.channel); + stubs->leaseServiceStub = Lease::NewStub(client.grpc_channel()); etcdv3::ActionParameters params; params.auth_token.assign(client.current_auth_token()); @@ -33,7 +33,7 @@ etcd::KeepAlive::KeepAlive(Client const &client, int ttl, int64_t lease_id): continue_next.store(true); - stubs->call.reset(new etcdv3::AsyncLeaseKeepAliveAction(params)); + stubs->call.reset(new etcdv3::AsyncLeaseKeepAliveAction(std::move(params))); task_ = std::thread([this]() { try { // start refresh @@ -47,28 +47,28 @@ etcd::KeepAlive::KeepAlive(Client const &client, int ttl, int64_t lease_id): } etcd::KeepAlive::KeepAlive(std::string const & address, int ttl, int64_t lease_id): - KeepAlive(Client(address), ttl, lease_id) { + KeepAlive(SyncClient(address), ttl, lease_id) { } etcd::KeepAlive::KeepAlive(std::string const & address, std::string const & username, std::string const & password, int ttl, int64_t lease_id, int const auth_token_ttl): - KeepAlive(Client(address, username, password, auth_token_ttl), ttl, lease_id) { + KeepAlive(SyncClient(address, username, password, auth_token_ttl), ttl, lease_id) { } -etcd::KeepAlive::KeepAlive(Client const &client, +etcd::KeepAlive::KeepAlive(SyncClient const &client, std::function const &handler, int ttl, int64_t lease_id): handler_(handler), ttl(ttl), lease_id(lease_id), continue_next(true) { stubs.reset(new EtcdServerStubs{}); - stubs->leaseServiceStub = Lease::NewStub(client.channel); + stubs->leaseServiceStub = Lease::NewStub(client.grpc_channel()); etcdv3::ActionParameters params; params.auth_token.assign(client.current_auth_token()); params.lease_id = this->lease_id; params.lease_stub = stubs->leaseServiceStub.get(); - stubs->call.reset(new etcdv3::AsyncLeaseKeepAliveAction(params)); + stubs->call.reset(new etcdv3::AsyncLeaseKeepAliveAction(std::move(params))); task_ = std::thread([this]() { try { // start refresh @@ -88,14 +88,14 @@ etcd::KeepAlive::KeepAlive(Client const &client, etcd::KeepAlive::KeepAlive(std::string const & address, std::function const &handler, int ttl, int64_t lease_id): - KeepAlive(Client(address), handler, ttl, lease_id) { + KeepAlive(SyncClient(address), handler, ttl, lease_id) { } etcd::KeepAlive::KeepAlive(std::string const & address, std::string const & username, std::string const & password, std::function const &handler, int ttl, int64_t lease_id, const int auth_token_ttl): - KeepAlive(Client(address, username, password, auth_token_ttl), handler, ttl, lease_id) { + KeepAlive(SyncClient(address, username, password, auth_token_ttl), handler, ttl, lease_id) { } etcd::KeepAlive::~KeepAlive() diff --git a/src/SyncClient.cpp b/src/SyncClient.cpp index 61c6dc8..ebda8a7 100644 --- a/src/SyncClient.cpp +++ b/src/SyncClient.cpp @@ -1,219 +1,1106 @@ -#include "etcd/SyncClient.hpp" +#include +#include +#include +#include +#include "etcd/Value.hpp" -#define CHECK_EXCEPTIONS(cmd) \ - try \ - { \ - return cmd; \ - } \ - catch (std::exception const & ex) \ - { \ - return etcd::Response(500, ex.what()); \ +#if defined(_WIN32) +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include "proto/rpc.grpc.pb.h" +#include "proto/v3lock.grpc.pb.h" +#include "proto/v3election.grpc.pb.h" + +#include "etcd/SyncClient.hpp" +#include "etcd/KeepAlive.hpp" +#include "etcd/v3/action_constants.hpp" +#include "etcd/v3/Action.hpp" +#include "etcd/v3/AsyncRangeResponse.hpp" +#include "etcd/v3/AsyncWatchResponse.hpp" +#include "etcd/v3/AsyncDeleteResponse.hpp" +#include "etcd/v3/AsyncPutResponse.hpp" +#include "etcd/v3/AsyncLockResponse.hpp" +#include "etcd/v3/AsyncElectionResponse.hpp" +#include "etcd/v3/AsyncTxnResponse.hpp" +#include "etcd/v3/Transaction.hpp" + +#include "etcd/v3/AsyncSetAction.hpp" +#include "etcd/v3/AsyncCompareAndSwapAction.hpp" +#include "etcd/v3/AsyncCompareAndDeleteAction.hpp" +#include "etcd/v3/AsyncUpdateAction.hpp" +#include "etcd/v3/AsyncHeadAction.hpp" +#include "etcd/v3/AsyncRangeAction.hpp" +#include "etcd/v3/AsyncDeleteAction.hpp" +#include "etcd/v3/AsyncPutAction.hpp" +#include "etcd/v3/AsyncWatchAction.hpp" +#include "etcd/v3/AsyncLeaseAction.hpp" +#include "etcd/v3/AsyncLockAction.hpp" +#include "etcd/v3/AsyncElectionAction.hpp" +#include "etcd/v3/AsyncTxnAction.hpp" + +namespace etcd { +namespace detail { + +static bool dns_resolve(std::string const &target, std::vector &endpoints) { + struct addrinfo hints = {}, *addrs; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + std::vector target_parts; + boost::split(target_parts, target, boost::is_any_of(":")); + if (target_parts.size() != 2) { + std::cerr << "warn: invalid URL: " << target << std::endl; + return false; } -etcd::SyncClient::SyncClient(std::string const & address, std::string const & load_balancer) - : client(address, load_balancer) -{ +#if defined(_WIN32) + { + // Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h. + WORD wVersionRequested = MAKEWORD(2, 2); + WSADATA wsaData; + + int err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + // Tell the user that we could not find a usable Winsock DLL. + std::cerr << "WSAStartup failed with error: %d" << err << std::endl; + return false; + } + } +#endif + + int r = getaddrinfo(target_parts[0].c_str(), target_parts[1].c_str(), &hints, &addrs); + if (r != 0) { + std::cerr << "warn: getaddrinfo() failed for endpoint " << target + << " with error: " << r << std::endl; + return false; + } + + char host[16] = {'\0'}; + for (struct addrinfo* addr = addrs; addr != nullptr; addr = addr->ai_next) { + memset(host, '\0', sizeof(host)); + getnameinfo(addr->ai_addr, addr->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST); + endpoints.emplace_back(std::string(host) + ":" + target_parts[1]); + } + freeaddrinfo(addrs); + return true; +} + +const std::string strip_and_resolve_addresses(std::string const &address) { + std::vector addresses; + boost::algorithm::split(addresses, address, boost::algorithm::is_any_of(",;")); + std::string stripped_address; + { + std::vector stripped_addresses; + std::string substr("://"); + for (auto const &addr: addresses) { + std::string::size_type idx = addr.find(substr); + std::string target = idx == std::string::npos ? addr : addr.substr(idx + substr.length()); + etcd::detail::dns_resolve(target, stripped_addresses); + } + stripped_address = boost::algorithm::join(stripped_addresses, ","); + } + return "ipv4:///" + stripped_address; +} + +const bool authenticate(std::shared_ptr const &channel, + std::string const &username, + std::string const &password, + std::string &token_or_message) { + // run a round of auth + auto auth_stub = etcdserverpb::Auth::NewStub(channel); + ClientContext context; + etcdserverpb::AuthenticateRequest auth_request; + etcdserverpb::AuthenticateResponse auth_response; + auth_request.set_name(username); + auth_request.set_password(password); + auto status = auth_stub->Authenticate(&context, auth_request, &auth_response); + if (status.ok()) { + token_or_message = auth_response.token(); + return true; + } else { + token_or_message = status.error_message(); + return false; + } +} + +static std::string read_from_file(std::string const &filename) { + std::ifstream file(filename.c_str(), std::ios::in); + if (file.is_open()) { + std::stringstream ss; + ss << file.rdbuf (); + file.close (); + return ss.str (); + } + return std::string{}; +} + +static grpc::SslCredentialsOptions make_ssl_credentials(std::string const &ca, + std::string const &cert, + std::string const &key) { + grpc::SslCredentialsOptions options; + options.pem_root_certs = read_from_file(ca); + options.pem_cert_chain = read_from_file(cert); + options.pem_private_key = read_from_file(key); + return options; +} + +template +std::unique_ptr make_unique_ptr(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +} +} + +class etcd::SyncClient::TokenAuthenticator { + private: + std::shared_ptr channel_; + std::string username_, password_, token_; + int ttl_ = 300; // see also --auth-token-ttl for etcd + std::chrono::time_point updated_at; + std::mutex mtx_; + bool has_token_ = false; + + public: + TokenAuthenticator(): has_token_(false) { + } + + TokenAuthenticator(std::shared_ptr channel, + std::string const &username, + std::string const &password, + const int ttl=300) + : channel_(channel), username_(username), password_(password), ttl_(ttl), has_token_(false) { + if ((!username.empty()) && (!(password.empty()))) { + has_token_ = true; + renew_if_expired(true); + } + } + + std::string const &renew_if_expired(const bool force = false) { + if (!has_token_) { + return token_; + } + std::lock_guard scoped_lock(mtx_); + if (force || (!token_.empty())) { + auto tp = std::chrono::system_clock::now(); + if (force || std::chrono::duration_cast(tp - updated_at).count() + > std::max(1, ttl_ - 3)) { + updated_at = tp; + // auth + if (!etcd::detail::authenticate(this->channel_, username_, password_, token_)) { + throw std::invalid_argument("Etcd authentication failed: " + token_); + } + } + } + return token_; + } +}; + +void etcd::SyncClient::TokenAuthenticatorDeleter::operator()(etcd::SyncClient::TokenAuthenticator *authenticator) { + if (authenticator) { + delete authenticator; + } +} + +struct etcd::SyncClient::EtcdServerStubs { + std::unique_ptr kvServiceStub; + std::unique_ptr watchServiceStub; + std::unique_ptr leaseServiceStub; + std::unique_ptr lockServiceStub; + std::unique_ptr electionServiceStub; +}; + +void etcd::SyncClient::EtcdServerStubsDeleter::operator()(etcd::SyncClient::EtcdServerStubs *stubs) { + if (stubs) { + delete stubs; + } } etcd::SyncClient::SyncClient(std::string const & address, - std::string const & username, - std::string const & password, - const int auth_token_ttl, - std::string const & load_balancer) - : client(address, username, password, auth_token_ttl, load_balancer) + std::string const & load_balancer) { + // create channels + std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); + grpc::ChannelArguments grpc_args; + grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); + grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); + std::shared_ptr creds = grpc::InsecureChannelCredentials(); + grpc_args.SetLoadBalancingPolicyName(load_balancer); + this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); + this->token_authenticator.reset(new TokenAuthenticator()); + + // create stubs + stubs.reset(new EtcdServerStubs{}); + stubs->kvServiceStub = KV::NewStub(this->channel); + stubs->watchServiceStub= Watch::NewStub(this->channel); + stubs->leaseServiceStub= Lease::NewStub(this->channel); + stubs->lockServiceStub = Lock::NewStub(this->channel); + stubs->electionServiceStub = Election::NewStub(this->channel); } -etcd::Response etcd::SyncClient::head() +etcd::SyncClient::SyncClient(std::string const & address, + #if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ) { - CHECK_EXCEPTIONS(client.head().get()); + // create channels + std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); + grpc::ChannelArguments grpc_args = arguments; + grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); + grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); + std::shared_ptr creds = grpc::InsecureChannelCredentials(); + this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); + this->token_authenticator.reset(new TokenAuthenticator()); + + // create stubs + stubs.reset(new EtcdServerStubs{}); + stubs->kvServiceStub = KV::NewStub(this->channel); + stubs->watchServiceStub= Watch::NewStub(this->channel); + stubs->leaseServiceStub= Lease::NewStub(this->channel); + stubs->lockServiceStub = Lock::NewStub(this->channel); + stubs->electionServiceStub = Election::NewStub(this->channel); } -etcd::Response etcd::SyncClient::get(std::string const & key) +etcd::SyncClient *etcd::SyncClient::WithUrl(std::string const & etcd_url, + std::string const & load_balancer) { + return new etcd::SyncClient(etcd_url, load_balancer); +} + +etcd::SyncClient *etcd::SyncClient::WithUrl(std::string const & etcd_url, + #if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ) { + return new etcd::SyncClient(etcd_url, arguments); +} + +etcd::SyncClient::SyncClient(std::string const & address, + std::string const & username, + std::string const & password, + int const auth_token_ttl, + std::string const & load_balancer) { - CHECK_EXCEPTIONS(client.get(key).get()); + // create channels + std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); + grpc::ChannelArguments grpc_args; + grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); + grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); + std::shared_ptr creds = grpc::InsecureChannelCredentials(); + grpc_args.SetLoadBalancingPolicyName(load_balancer); + this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); + + // auth + this->token_authenticator.reset(new TokenAuthenticator(this->channel, username, password, auth_token_ttl)); + + // setup stubs + stubs.reset(new EtcdServerStubs{}); + stubs->kvServiceStub = KV::NewStub(this->channel); + stubs->watchServiceStub= Watch::NewStub(this->channel); + stubs->leaseServiceStub= Lease::NewStub(this->channel); + stubs->lockServiceStub = Lock::NewStub(this->channel); + stubs->electionServiceStub = Election::NewStub(this->channel); +} + +etcd::SyncClient::SyncClient(std::string const & address, + std::string const & username, + std::string const & password, + int const auth_token_ttl, + #if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + + ) +{ + // create channels + std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); + grpc::ChannelArguments grpc_args = arguments; + grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); + grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); + std::shared_ptr creds = grpc::InsecureChannelCredentials(); + this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); + + // auth + this->token_authenticator.reset(new TokenAuthenticator(this->channel, username, password, auth_token_ttl)); + + // setup stubs + stubs.reset(new EtcdServerStubs{}); + stubs->kvServiceStub = KV::NewStub(this->channel); + stubs->watchServiceStub= Watch::NewStub(this->channel); + stubs->leaseServiceStub= Lease::NewStub(this->channel); + stubs->lockServiceStub = Lock::NewStub(this->channel); + stubs->electionServiceStub = Election::NewStub(this->channel); +} + +etcd::SyncClient *etcd::SyncClient::WithUser(std::string const & etcd_url, + std::string const & username, + std::string const & password, + int const auth_token_ttl, + std::string const & load_balancer) { + return new etcd::SyncClient(etcd_url, username, password, auth_token_ttl, load_balancer); +} + +etcd::SyncClient *etcd::SyncClient::WithUser(std::string const & etcd_url, + std::string const & username, + std::string const & password, + int const auth_token_ttl, + #if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ) { + return new etcd::SyncClient(etcd_url, username, password, auth_token_ttl, arguments); +} + + +etcd::SyncClient::SyncClient(std::string const & address, + std::string const & ca, + std::string const & cert, + std::string const & key, + std::string const & target_name_override, + std::string const & load_balancer) +{ + // create channels + std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); + grpc::ChannelArguments grpc_args; + grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); + grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); + std::shared_ptr creds = grpc::SslCredentials( + etcd::detail::make_ssl_credentials(ca, cert, key)); + grpc_args.SetLoadBalancingPolicyName(load_balancer); + if (!target_name_override.empty()) { + grpc_args.SetString(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, target_name_override); + } + this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); + this->token_authenticator.reset(new TokenAuthenticator()); + + // setup stubs + stubs.reset(new EtcdServerStubs{}); + stubs->kvServiceStub = KV::NewStub(this->channel); + stubs->watchServiceStub= Watch::NewStub(this->channel); + stubs->leaseServiceStub= Lease::NewStub(this->channel); + stubs->lockServiceStub = Lock::NewStub(this->channel); + stubs->electionServiceStub = Election::NewStub(this->channel); +} + +etcd::SyncClient::SyncClient(std::string const & address, + std::string const & ca, + std::string const & cert, + std::string const & key, + std::string const & target_name_override, + #if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + + ) +{ + // create channels + std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); + grpc::ChannelArguments grpc_args = arguments; + grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); + grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); + std::shared_ptr creds = grpc::SslCredentials( + etcd::detail::make_ssl_credentials(ca, cert, key)); + if (!target_name_override.empty()) { + grpc_args.SetString(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, target_name_override); + } + this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); + this->token_authenticator.reset(new TokenAuthenticator()); + + // setup stubs + stubs.reset(new EtcdServerStubs{}); + stubs->kvServiceStub = KV::NewStub(this->channel); + stubs->watchServiceStub= Watch::NewStub(this->channel); + stubs->leaseServiceStub= Lease::NewStub(this->channel); + stubs->lockServiceStub = Lock::NewStub(this->channel); + stubs->electionServiceStub = Election::NewStub(this->channel); +} + +etcd::SyncClient *etcd::SyncClient::WithSSL(std::string const & etcd_url, + std::string const & ca, + std::string const & cert, + std::string const & key, + std::string const & target_name_override, + std::string const & load_balancer) { + return new etcd::SyncClient(etcd_url, ca, cert, key, target_name_override, load_balancer); +} + +etcd::SyncClient *etcd::SyncClient::WithSSL(std::string const & etcd_url, + #if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments, +#else + grpc_impl::ChannelArguments const & arguments, +#endif + std::string const & ca, + std::string const & cert, + std::string const & key, + std::string const & target_name_override) { + return new etcd::SyncClient(etcd_url, ca, cert, key, target_name_override, arguments); +} + +etcd::SyncClient::~SyncClient() { + stubs.reset(); + channel.reset(); +} + +/** + * Note [lease with TTL and issue the actual request] + * + * We sometime use the request like `set(key, value, TTL)`, we explain the TTL as + * the time between the user call the `set()` method between the request is + * actually executed in etcd server. Thus, we issue a lease request with that TTL + * value immediately, and pass it to the `set_internal()` method, the later may + * be issues asynchronously. + * + * Thus the TTL could keep the expected semantic even in the async runtime. + */ + +etcd::Response etcd::SyncClient::head() { + return Response::create(this->head_internal()); +} + +std::shared_ptr etcd::SyncClient::head_internal() +{ + etcdv3::ActionParameters params; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params)); +} + +etcd::Response etcd::SyncClient::get(std::string const &key) { + return Response::create(this->get_internal(key)); +} + +std::shared_ptr etcd::SyncClient::get_internal(std::string const & key) +{ + etcdv3::ActionParameters params; + params.key.assign(key); + params.withPrefix = false; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params)); } etcd::Response etcd::SyncClient::set(std::string const & key, std::string const & value, int ttl) { - CHECK_EXCEPTIONS(client.set(key, value, ttl).get()); + // See Note [lease with TTL and issue the actual request] + int64_t leaseId = 0; + if(ttl > 0) + { + auto res = this->leasegrant(ttl); + if(!res.is_ok()) + { + return etcd::Response(res.error_code(), res.error_message().c_str()); + } + else + { + leaseId = res.value().lease(); + } + } + return Response::create(this->set_internal(key, value, leaseId)); } -etcd::Response etcd::SyncClient::set(std::string const & key, std::string const & value, int64_t leaseId) +etcd::Response etcd::SyncClient::set(std::string const & key, std::string const & value, int64_t leaseid) { - CHECK_EXCEPTIONS(client.set(key, value, leaseId).get()); + return Response::create(this->set_internal(key, value, leaseid)); +} + +std::shared_ptr etcd::SyncClient::set_internal(std::string const & key, std::string const & value, int64_t leaseid) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.value.assign(value); + params.lease_id = leaseid; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params), false); } etcd::Response etcd::SyncClient::add(std::string const & key, std::string const & value, int ttl) { - CHECK_EXCEPTIONS(client.add(key, value, ttl).get()); + // See Note [lease with TTL and issue the actual request] + int64_t leaseId = 0; + if(ttl > 0) + { + auto res = this->leasegrant(ttl); + if(!res.is_ok()) + { + return etcd::Response(res.error_code(), res.error_message().c_str()); + } + else + { + leaseId = res.value().lease(); + } + } + return Response::create(this->add_internal(key, value, leaseId)); } -etcd::Response etcd::SyncClient::add(std::string const & key, std::string const & value, int64_t leaseId) +etcd::Response etcd::SyncClient::add(std::string const & key, std::string const & value, int64_t leaseid) { - CHECK_EXCEPTIONS(client.add(key, value, leaseId).get()); + return Response::create(this->add_internal(key, value, leaseid)); } -etcd::Response etcd::SyncClient::put(std::string const & key, std::string const & value) -{ - CHECK_EXCEPTIONS(client.put(key, value).get()); +std::shared_ptr etcd::SyncClient::add_internal(std::string const & key, std::string const & value, int64_t leaseid) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.value.assign(value); + params.lease_id = leaseid; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params), true); +} + +etcd::Response etcd::SyncClient::put(std::string const & key, std::string const & value) { + return Response::create(this->put_internal(key, value)); +} + +std::shared_ptr etcd::SyncClient::put_internal(std::string const & key, std::string const & value) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.value.assign(value); + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params)); } etcd::Response etcd::SyncClient::modify(std::string const & key, std::string const & value, int ttl) { - CHECK_EXCEPTIONS(client.modify(key, value, ttl).get()); + // See Note [lease with TTL and issue the actual request] + int64_t leaseId = 0; + if(ttl > 0) + { + auto res = leasegrant(ttl); + if(!res.is_ok()) + { + return etcd::Response(res.error_code(), res.error_message().c_str()); + } + else + { + leaseId = res.value().lease(); + } + } + return Response::create(this->modify_internal(key, value, leaseId)); } -etcd::Response etcd::SyncClient::modify(std::string const & key, std::string const & value, int64_t leaseId) +etcd::Response etcd::SyncClient::modify(std::string const & key, std::string const & value, int64_t leaseid) { - CHECK_EXCEPTIONS(client.modify(key, value, leaseId).get()); + return Response::create(this->modify_internal(key, value, leaseid)); +} + +std::shared_ptr etcd::SyncClient::modify_internal(std::string const & key, std::string const & value, int64_t leaseid) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.value.assign(value); + params.lease_id = leaseid; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params)); } etcd::Response etcd::SyncClient::modify_if(std::string const & key, std::string const & value, std::string const & old_value, int ttl) { - CHECK_EXCEPTIONS(client.modify_if(key, value, old_value, ttl).get()); + // See Note [lease with TTL and issue the actual request] + int64_t leaseId = 0; + if(ttl > 0) + { + auto res = leasegrant(ttl); + if(!res.is_ok()) + { + return etcd::Response(res.error_code(), res.error_message().c_str()); + } + else + { + leaseId = res.value().lease(); + } + } + return Response::create(this->modify_if_internal(key, value, 0, old_value, leaseId, etcdv3::AtomicityType::PREV_VALUE)); } -etcd::Response etcd::SyncClient::modify_if(std::string const & key, std::string const & value, std::string const & old_value, int64_t leaseId) +etcd::Response etcd::SyncClient::modify_if(std::string const & key, std::string const & value, std::string const & old_value, int64_t leaseid) { - CHECK_EXCEPTIONS(client.modify_if(key, value, old_value, leaseId).get()); + return Response::create(this->modify_if_internal(key, value, 0, old_value, leaseid, etcdv3::AtomicityType::PREV_VALUE)); } etcd::Response etcd::SyncClient::modify_if(std::string const & key, std::string const & value, int64_t old_index, int ttl) { - CHECK_EXCEPTIONS(client.modify_if(key, value, old_index, ttl).get()); + // See Note [lease with TTL and issue the actual request] + int64_t leaseId = 0; + if(ttl > 0) + { + auto res = leasegrant(ttl); + if(!res.is_ok()) + { + return etcd::Response(res.error_code(), res.error_message().c_str()); + } + else + { + leaseId = res.value().lease(); + } + } + return Response::create(this->modify_if_internal(key, value, old_index, "", leaseId, etcdv3::AtomicityType::PREV_INDEX)); } -etcd::Response etcd::SyncClient::modify_if(std::string const & key, std::string const & value, int64_t old_index, int64_t leaseId) +etcd::Response etcd::SyncClient::modify_if(std::string const & key, std::string const & value, int64_t old_index, int64_t leaseid) { - CHECK_EXCEPTIONS(client.modify_if(key, value, old_index, leaseId).get()); + return Response::create(this->modify_if_internal(key, value, old_index, "", leaseid, etcdv3::AtomicityType::PREV_INDEX)); +} + +std::shared_ptr etcd::SyncClient::modify_if_internal(std::string const & key, std::string const & value, int64_t old_index, std::string const & old_value, int64_t leaseId, etcdv3::AtomicityType const & atomicity_type) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.value.assign(value); + params.lease_id = leaseId; + params.old_revision = old_index; + params.old_value = old_value; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params), atomicity_type); } etcd::Response etcd::SyncClient::rm(std::string const & key) { - CHECK_EXCEPTIONS(client.rm(key).get()); + return Response::create(this->rm_internal(key)); +} + +std::shared_ptr etcd::SyncClient::rm_internal(std::string const & key) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.withPrefix = false; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params)); } etcd::Response etcd::SyncClient::rm_if(std::string const & key, std::string const & old_value) { - CHECK_EXCEPTIONS(client.rm_if(key, old_value).get()); + return Response::create(this->rm_if_internal(key, 0, old_value, etcdv3::AtomicityType::PREV_VALUE)); } etcd::Response etcd::SyncClient::rm_if(std::string const & key, int64_t old_index) { - CHECK_EXCEPTIONS(client.rm_if(key, old_index).get()); + return Response::create(this->rm_if_internal(key, old_index, "", etcdv3::AtomicityType::PREV_INDEX)); } +std::shared_ptr etcd::SyncClient::rm_if_internal(std::string const & key, int64_t old_index, const std::string & old_value, etcdv3::AtomicityType const & atomicity_type) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.withPrefix = false; + params.old_revision = old_index; + params.old_value = old_value; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params), atomicity_type); +} etcd::Response etcd::SyncClient::rmdir(std::string const & key, bool recursive) { - CHECK_EXCEPTIONS(client.rmdir(key, recursive).get()); + return Response::create(this->rmdir_internal(key, recursive)); +} + +std::shared_ptr etcd::SyncClient::rmdir_internal(std::string const & key, bool recursive) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.withPrefix = recursive; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params)); } etcd::Response etcd::SyncClient::rmdir(std::string const & key, const char *range_end) { - CHECK_EXCEPTIONS(client.rmdir(key, range_end).get()); + return rmdir(key, std::string(range_end)); } etcd::Response etcd::SyncClient::rmdir(std::string const & key, std::string const &range_end) { - CHECK_EXCEPTIONS(client.rmdir(key, range_end).get()); + return Response::create(this->rmdir_internal(key, range_end)); +} + +std::shared_ptr etcd::SyncClient::rmdir_internal(std::string const & key, std::string const &range_end) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.range_end.assign(range_end); + params.withPrefix = false; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params)); } etcd::Response etcd::SyncClient::ls(std::string const & key) { - CHECK_EXCEPTIONS(client.ls(key).get()); + return Response::create(this->ls_internal(key, 0 /* default: no limit */)); } etcd::Response etcd::SyncClient::ls(std::string const & key, size_t const limit) { - CHECK_EXCEPTIONS(client.ls(key, limit).get()); + return Response::create(this->ls_internal(key, limit)); +} + +std::shared_ptr etcd::SyncClient::ls_internal(std::string const & key, size_t const limit) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.withPrefix = true; + params.limit = limit; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params)); } etcd::Response etcd::SyncClient::ls(std::string const & key, std::string const &range_end) { - CHECK_EXCEPTIONS(client.ls(key, range_end).get()); + return Response::create(this->ls_internal(key, range_end, 0 /* default: no limit */)); } -etcd::Response etcd::SyncClient::ls(std::string const & key, std::string const &range_end, size_t limit) +etcd::Response etcd::SyncClient::ls(std::string const & key, std::string const &range_end, size_t const limit) { - CHECK_EXCEPTIONS(client.ls(key, range_end, limit).get()); + return Response::create(this->ls_internal(key, range_end, limit)); } -etcd::Response etcd::SyncClient::leasegrant(int ttl) -{ - CHECK_EXCEPTIONS(client.leasegrant(ttl).get()); -} - -etcd::Response etcd::SyncClient::leaserevoke(int64_t lease_id) -{ - CHECK_EXCEPTIONS(client.leaserevoke(lease_id).get()); -} - -etcd::Response etcd::SyncClient::leasetimetolive(int64_t lease_id) -{ - CHECK_EXCEPTIONS(client.leasetimetolive(lease_id).get()); -} - -etcd::Response etcd::SyncClient::campaign(std::string const &name, int64_t lease_id, - std::string const &value) -{ - CHECK_EXCEPTIONS(client.campaign(name, lease_id, value).get()); -} - -etcd::Response etcd::SyncClient::proclaim(std::string const &name, int64_t lease_id, - std::string const &key, int64_t revision, - std::string const &value) -{ - CHECK_EXCEPTIONS(client.proclaim(name, lease_id, key, revision, value).get()); -} - -etcd::Response etcd::SyncClient::leader(std::string const &name) -{ - CHECK_EXCEPTIONS(client.leader(name).get()); -} - -std::unique_ptr etcd::SyncClient::observe( - std::string const &name, const bool once) -{ - return client.observe(name, once); -} - -std::unique_ptr etcd::SyncClient::observe( - std::string const &name, - std::function callback, - const bool once) -{ - return client.observe(name, callback, once); -} - -etcd::Response etcd::SyncClient::resign(std::string const &name, int64_t lease_id, - std::string const &key, int64_t revision) -{ - CHECK_EXCEPTIONS(client.resign(name, lease_id, key, revision).get()); +std::shared_ptr etcd::SyncClient::ls_internal(std::string const & key, std::string const &range_end, size_t const limit) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.range_end.assign(range_end); + params.withPrefix = false; + params.limit = limit; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params)); } etcd::Response etcd::SyncClient::watch(std::string const & key, bool recursive) { - CHECK_EXCEPTIONS(client.watch(key, recursive).get()); + return Response::create(this->watch_internal(key, 0 /* from current location */, recursive)); } etcd::Response etcd::SyncClient::watch(std::string const & key, int64_t fromIndex, bool recursive) { - CHECK_EXCEPTIONS(client.watch(key, fromIndex, recursive).get()); + return Response::create(this->watch_internal(key, fromIndex, recursive)); +} + +std::shared_ptr etcd::SyncClient::watch_internal(std::string const & key, int64_t fromIndex, bool recursive) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.withPrefix = recursive; + params.revision = fromIndex; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.watch_stub = stubs->watchServiceStub.get(); + return std::make_shared(std::move(params)); } etcd::Response etcd::SyncClient::watch(std::string const & key, const char *range_end) { - CHECK_EXCEPTIONS(client.watch(key, range_end).get()); + return watch(key, std::string(range_end)); } -etcd::Response etcd::SyncClient::watch(std::string const & key, std::string const &range_end) +etcd::Response etcd::SyncClient::watch(std::string const & key, std::string const & range_end) { - CHECK_EXCEPTIONS(client.watch(key, range_end).get()); + return Response::create(this->watch_internal(key, range_end, 0 /* from current location */)); } -etcd::Response etcd::SyncClient::watch(std::string const & key, std::string const &range_end, int64_t fromIndex) +etcd::Response etcd::SyncClient::watch(std::string const & key, std::string const & range_end, int64_t fromIndex) { - CHECK_EXCEPTIONS(client.watch(key, range_end, fromIndex).get()); + return Response::create(this->watch_internal(key, range_end, fromIndex)); +} + +std::shared_ptr etcd::SyncClient::watch_internal(std::string const & key, std::string const &range_end, int64_t fromIndex) { + etcdv3::ActionParameters params; + params.key.assign(key); + params.range_end.assign(range_end); + params.withPrefix = false; + params.revision = fromIndex; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.watch_stub = stubs->watchServiceStub.get(); + return std::make_shared(std::move(params)); +} + +etcd::Response etcd::SyncClient::leasegrant(int ttl) +{ + // lease grant is special, that we are expected the callback could be invoked + // immediately after the lease is granted by the server. + // + // otherwise when we get the response, the lease might already has expired. + return Response::create([this, ttl]() { + etcdv3::ActionParameters params; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.lease_stub = stubs->leaseServiceStub.get(); + params.ttl = ttl; + return std::make_shared(std::move(params)); + }); +} + +std::shared_ptr etcd::SyncClient::leasekeepalive(int ttl) { + etcdv3::ActionParameters params; + params.ttl = ttl; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.lease_stub = stubs->leaseServiceStub.get(); + + // keep alive is synchronous in two folds: + // + // - the wait-for-response starts immediately after the request been issued + // - the keep alive thread starts immediately after the lease been granted + auto call = std::make_shared(std::move(params)); + call->waitForResponse(); + auto v3resp = call->ParseResponse(); + return std::make_shared(*this, ttl, v3resp.get_value().kvs.lease()); +} + +etcd::Response etcd::SyncClient::leaserevoke(int64_t lease_id) +{ + return Response::create(this->leaserevoke_internal(lease_id)); +} + +std::shared_ptr etcd::SyncClient::leaserevoke_internal(int64_t lease_id) { + etcdv3::ActionParameters params; + params.lease_id = lease_id; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.lease_stub = stubs->leaseServiceStub.get(); + return std::make_shared(std::move(params)); +} + +etcd::Response etcd::SyncClient::leasetimetolive(int64_t lease_id) +{ + return Response::create(this->leasetimetolive_internal(lease_id)); +} + +std::shared_ptr etcd::SyncClient::leasetimetolive_internal(int64_t lease_id) { + etcdv3::ActionParameters params; + params.lease_id = lease_id; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.lease_stub = stubs->leaseServiceStub.get(); + return std::make_shared(std::move(params)); +} + +etcd::Response etcd::SyncClient::lock(std::string const &key) { + // routines in lock usually will be fast, less than 10 seconds. + // + // (base on our experiences in vineyard and GraphScope). + static const int DEFAULT_LEASE_TTL_FOR_LOCK = 10; + return this->lock(key, DEFAULT_LEASE_TTL_FOR_LOCK); +} + +etcd::Response etcd::SyncClient::lock(std::string const &key, int lease_ttl) { + auto keepalive = this->leasekeepalive(lease_ttl); + return this->lock_internal(key, keepalive); +} + +etcd::Response etcd::SyncClient::lock_internal(std::string const &key, std::shared_ptr const &keepalive) { + etcdv3::ActionParameters params; + params.key = key; + params.lease_id = keepalive->Lease(); + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.lock_stub = stubs->lockServiceStub.get(); + + { + std::lock_guard lexical_scope_lock(mutex_for_keepalives); + this->keep_alive_for_locks[keepalive->Lease()] = keepalive; + } + // synchronously wait the lock response to avoid deadlock + auto call = std::make_shared(std::move(params)); + auto lock_resp = Response::create(std::move(call)); + // attach the lease id to the lock response + lock_resp._value.leaseId = keepalive->Lease(); + { + std::lock_guard lexical_scope_lock(mutex_for_keepalives); + if (lock_resp.is_ok()) { + this->leases_for_locks[lock_resp.lock_key()] = keepalive->Lease(); + } else { + this->keep_alive_for_locks.erase(keepalive->Lease()); + } + } + return lock_resp; +} + +etcd::Response etcd::SyncClient::lock_with_lease(std::string const &key, + int64_t lease_id) { + return Response::create(this->lock_with_lease_internal(key, lease_id)); +} + +std::shared_ptr etcd::SyncClient::lock_with_lease_internal(std::string const &key, int64_t lease_id) { + etcdv3::ActionParameters params; + params.key = key; + params.lease_id = lease_id; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.lock_stub = stubs->lockServiceStub.get(); + return std::make_shared(std::move(params)); +} + +etcd::Response etcd::SyncClient::unlock(std::string const &lock_key) { + return Response::create(this->unlock_internal(lock_key)); +} + +std::shared_ptr etcd::SyncClient::unlock_internal(std::string const &lock_key) { + etcdv3::ActionParameters params; + params.key = lock_key; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.lock_stub = stubs->lockServiceStub.get(); + + // issue a "unlock" request + auto call = std::make_shared(std::move(params)); + + // cancel the KeepAlive first, if it exists + { + std::lock_guard lexical_scope_lock(mutex_for_keepalives); + auto p_leases = this->leases_for_locks.find(lock_key); + int64_t lock_lease_id = 0; + if (p_leases != this->leases_for_locks.end()) { + auto p_keeps_alive = this->keep_alive_for_locks.find(p_leases->second); + if (p_keeps_alive != this->keep_alive_for_locks.end()) { + this->keep_alive_for_locks.erase(p_keeps_alive); + } else { +#if !defined(NDEBUG) + std::cerr << "Keepalive for lease not found" << std::endl; +#endif + } + lock_lease_id = p_leases->second; + this->leases_for_locks.erase(p_leases); + } else { +#if !defined(NDEBUG) + std::cerr << "Lease for lock not found" << std::endl; +#endif + } + if (lock_lease_id != 0) { + this->leaserevoke(lock_lease_id); + } + } + + // asynchronously wait. + return call; +} + +etcd::Response etcd::SyncClient::txn(etcdv3::Transaction const &txn) { + return Response::create(this->txn_internal(txn)); +} + +std::shared_ptr etcd::SyncClient::txn_internal(etcdv3::Transaction const &txn) { + etcdv3::ActionParameters params; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.kv_stub = stubs->kvServiceStub.get(); + return std::make_shared(std::move(params), txn); +} + +etcd::Response etcd::SyncClient::campaign( + std::string const &name, int64_t lease_id, std::string const &value) { + return Response::create(this->campaign_internal(name, lease_id, value)); +} + +std::shared_ptr etcd::SyncClient::campaign_internal( + std::string const &name, int64_t lease_id, std::string const &value) { + etcdv3::ActionParameters params; + params.name = name; + params.lease_id = lease_id; + params.value = value; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.election_stub = stubs->electionServiceStub.get(); + return std::make_shared(std::move(params)); +} + +etcd::Response etcd::SyncClient::proclaim( + std::string const &name, int64_t lease_id, + std::string const &key, int64_t revision, std::string const &value) { + return Response::create(this->proclaim_internal(name, lease_id, key, revision, value)); +} + +std::shared_ptr etcd::SyncClient::proclaim_internal( + std::string const &name, int64_t lease_id, + std::string const &key, int64_t revision, std::string const &value) { + etcdv3::ActionParameters params; + params.name = name; + params.lease_id = lease_id; + params.key = key; + params.revision = revision; + params.value = value; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.election_stub = stubs->electionServiceStub.get(); + return std::make_shared(std::move(params)); +} + +etcd::Response etcd::SyncClient::leader(std::string const &name) { + return Response::create(this->leader_internal(name)); +} + +std::shared_ptr etcd::SyncClient::leader_internal(std::string const &name) { + etcdv3::ActionParameters params; + params.name = name; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.election_stub = stubs->electionServiceStub.get(); + return std::make_shared(std::move(params)); +} + +std::unique_ptr etcd::SyncClient::observe( + std::string const &name) { + etcdv3::ActionParameters params; + params.name.assign(name); + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.election_stub = stubs->electionServiceStub.get(); + std::unique_ptr observer(new Observer()); + observer->action = std::make_shared(std::move(params)); + return observer; +} + +etcd::Response etcd::SyncClient::resign( + std::string const &name, int64_t lease_id, std::string const &key, int64_t revision) { + return Response::create(this->resign_internal(name, lease_id, key, revision)); +} + +std::shared_ptr etcd::SyncClient::resign_internal(std::string const &name, int64_t lease_id, + std::string const &key, int64_t revision) { + etcdv3::ActionParameters params; + params.name = name; + params.lease_id = lease_id; + params.key = key; + params.revision = revision; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.election_stub = stubs->electionServiceStub.get(); + return std::make_shared(std::move(params)); +} + +const std::string &etcd::SyncClient::current_auth_token() const { + return this->token_authenticator->renew_if_expired(); +} + +std::shared_ptr etcd::SyncClient::grpc_channel() const { + return this->channel; +} + +etcd::SyncClient::Observer::~Observer() { + if (this->action != nullptr) { + this->action->CancelObserve(); + this->action = nullptr; + } +} + +etcd::Response etcd::SyncClient::Observer::WaitOnce() { + if (this->action != nullptr) { + return Response::create(this->action); + } else { + return Response{}; + } } diff --git a/src/Watcher.cpp b/src/Watcher.cpp index 8522396..eba8523 100644 --- a/src/Watcher.cpp +++ b/src/Watcher.cpp @@ -12,18 +12,18 @@ void etcd::Watcher::EtcdServerStubsDeleter::operator()(etcd::Watcher::EtcdServer } } -etcd::Watcher::Watcher(Client const &client, std::string const & key, +etcd::Watcher::Watcher(SyncClient const &client, std::string const & key, std::function callback, bool recursive): Watcher(client, key, -1, callback, recursive) { } -etcd::Watcher::Watcher(Client const &client, std::string const & key, +etcd::Watcher::Watcher(SyncClient const &client, std::string const & key, std::string const &range_end, std::function callback): Watcher(client, key, range_end, -1, callback) { } -etcd::Watcher::Watcher(Client const &client, std::string const & key, int64_t fromIndex, +etcd::Watcher::Watcher(SyncClient const &client, std::string const & key, int64_t fromIndex, std::function callback, bool recursive): fromIndex(fromIndex), recursive(recursive) { stubs.reset(new EtcdServerStubs{}); @@ -31,7 +31,7 @@ etcd::Watcher::Watcher(Client const &client, std::string const & key, int64_t fr doWatch(key, "", client.current_auth_token(), callback); } -etcd::Watcher::Watcher(Client const &client, std::string const & key, +etcd::Watcher::Watcher(SyncClient const &client, std::string const & key, std::string const &range_end, int64_t fromIndex, std::function callback): fromIndex(fromIndex), recursive(false) { @@ -53,13 +53,13 @@ etcd::Watcher::Watcher(std::string const & address, std::string const & key, etcd::Watcher::Watcher(std::string const & address, std::string const & key, int64_t fromIndex, std::function callback, bool recursive): - Watcher(Client(address), key, fromIndex, callback, recursive) { + Watcher(SyncClient(address), key, fromIndex, callback, recursive) { } etcd::Watcher::Watcher(std::string const & address, std::string const & key, std::string const & range_end, int64_t fromIndex, std::function callback): - Watcher(Client(address), key, range_end, fromIndex, callback) { + Watcher(SyncClient(address), key, range_end, fromIndex, callback) { } etcd::Watcher::Watcher(std::string const & address, @@ -83,7 +83,7 @@ etcd::Watcher::Watcher(std::string const & address, std::string const & key, int64_t fromIndex, std::function callback, bool recursive, int const auth_token_ttl): - Watcher(Client(address, username, password, auth_token_ttl), key, fromIndex, callback, recursive) { + Watcher(SyncClient(address, username, password, auth_token_ttl), key, fromIndex, callback, recursive) { } etcd::Watcher::Watcher(std::string const & address, @@ -91,7 +91,7 @@ etcd::Watcher::Watcher(std::string const & address, std::string const & key, std::string const & range_end, int64_t fromIndex, std::function callback, int const auth_token_ttl): - Watcher(Client(address, username, password, auth_token_ttl), key, range_end, fromIndex, callback) { + Watcher(SyncClient(address, username, password, auth_token_ttl), key, range_end, fromIndex, callback) { } etcd::Watcher::~Watcher() @@ -144,15 +144,18 @@ void etcd::Watcher::doWatch(std::string const & key, params.withPrefix = recursive; params.watch_stub = stubs->watchServiceStub.get(); - stubs->call.reset(new etcdv3::AsyncWatchAction(params)); + stubs->call.reset(new etcdv3::AsyncWatchAction(std::move(params))); task_ = std::thread([this, callback]() { stubs->call->waitForResponse(callback); if (wait_callback != nullptr) { - // issue the callback in another thread to avoid deadlock, is ok to detach the pplx::task - pplx::task([this]() -> void { + // issue the callback in another thread (detached) to avoid deadlock, + // it is ok to detach a pplx::task, but we cannot use the pplx::task + // in the core library + std::thread canceller([this]() { wait_callback(stubs->call->Cancelled()); }); + canceller.detach(); } }); cancelled.store(false); diff --git a/src/v3/Action.cpp b/src/v3/Action.cpp index 463bd31..1755a9f 100644 --- a/src/v3/Action.cpp +++ b/src/v3/Action.cpp @@ -5,6 +5,16 @@ etcdv3::Action::Action(etcdv3::ActionParameters const ¶ms) { parameters = params; + this->InitAction(); +} + +etcdv3::Action::Action(etcdv3::ActionParameters && params) +{ + parameters = std::move(params); + this->InitAction(); +} + +void etcdv3::Action::InitAction() { if (!parameters.auth_token.empty()) { // use `token` as the key, see: // @@ -26,10 +36,26 @@ etcdv3::ActionParameters::ActionParameters() lease_stub = NULL; } +void etcdv3::ActionParameters::dump(std::ostream &os) const { + os << "ActionParameters:" << std::endl; + os << " withPrefix: " << withPrefix << std::endl; + os << " revision: " << revision << std::endl; + os << " old_revision: " << old_revision << std::endl; + os << " lease_id: " << lease_id << std::endl; + os << " ttl: " << ttl << std::endl; + os << " limit: " << limit << std::endl; + os << " name: " << name << std::endl; + os << " key: " << key << std::endl; + os << " range_end: " << range_end << std::endl; + os << " value: " << value << std::endl; + os << " old_value: " << old_value << std::endl; + os << " auth_token: " << auth_token << std::endl; +} + void etcdv3::Action::waitForResponse() { void* got_tag; - bool ok = false; + bool ok = false; cq_.Next(&got_tag, &ok); GPR_ASSERT(got_tag == (void*)this); diff --git a/src/v3/AsyncCompareAndDeleteAction.cpp b/src/v3/AsyncCompareAndDeleteAction.cpp index 29e6ab3..1045709 100644 --- a/src/v3/AsyncCompareAndDeleteAction.cpp +++ b/src/v3/AsyncCompareAndDeleteAction.cpp @@ -10,8 +10,8 @@ using etcdserverpb::ResponseOp; using etcdserverpb::TxnRequest; etcdv3::AsyncCompareAndDeleteAction::AsyncCompareAndDeleteAction( - etcdv3::ActionParameters const ¶m, etcdv3::AtomicityType type) - :etcdv3::Action(param) + etcdv3::ActionParameters && params, etcdv3::AtomicityType type) + :etcdv3::Action(std::move(params)) { etcdv3::Transaction transaction(parameters.key); if(type == etcdv3::AtomicityType::PREV_VALUE) diff --git a/src/v3/AsyncCompareAndSwapAction.cpp b/src/v3/AsyncCompareAndSwapAction.cpp index c8f4e68..43e9b69 100644 --- a/src/v3/AsyncCompareAndSwapAction.cpp +++ b/src/v3/AsyncCompareAndSwapAction.cpp @@ -10,8 +10,8 @@ using etcdserverpb::ResponseOp; using etcdserverpb::TxnRequest; etcdv3::AsyncCompareAndSwapAction::AsyncCompareAndSwapAction( - etcdv3::ActionParameters const ¶m, etcdv3::AtomicityType type) - : etcdv3::Action(param) + etcdv3::ActionParameters && params, etcdv3::AtomicityType type) + : etcdv3::Action(std::move(params)) { etcdv3::Transaction transaction(parameters.key); if(type == etcdv3::AtomicityType::PREV_VALUE) diff --git a/src/v3/AsyncDeleteAction.cpp b/src/v3/AsyncDeleteAction.cpp index 60e733c..a8b004c 100644 --- a/src/v3/AsyncDeleteAction.cpp +++ b/src/v3/AsyncDeleteAction.cpp @@ -4,8 +4,8 @@ using etcdserverpb::DeleteRangeRequest; etcdv3::AsyncDeleteAction::AsyncDeleteAction( - ActionParameters const ¶m) - : etcdv3::Action(param) + ActionParameters && params) + : etcdv3::Action(std::move(params)) { DeleteRangeRequest del_request; del_request.set_key(parameters.key); diff --git a/src/v3/AsyncElectionAction.cpp b/src/v3/AsyncElectionAction.cpp index 77dc624..b328997 100644 --- a/src/v3/AsyncElectionAction.cpp +++ b/src/v3/AsyncElectionAction.cpp @@ -1,4 +1,5 @@ #include "etcd/v3/AsyncElectionAction.hpp" +#include #include "etcd/v3/action_constants.hpp" @@ -14,13 +15,13 @@ using v3electionpb::ResignRequest; using v3electionpb::ResignResponse; etcdv3::AsyncCampaignAction::AsyncCampaignAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { CampaignRequest campaign_request; - campaign_request.set_name(param.name); - campaign_request.set_lease(param.lease_id); - campaign_request.set_value(param.value); + campaign_request.set_name(parameters.name); + campaign_request.set_lease(parameters.lease_id); + campaign_request.set_value(parameters.value); response_reader = parameters.election_stub->AsyncCampaign(&context, campaign_request, &cq_); response_reader->Finish(&reply, &status, (void *)this); @@ -42,18 +43,18 @@ etcdv3::AsyncCampaignResponse etcdv3::AsyncCampaignAction::ParseResponse() } etcdv3::AsyncProclaimAction::AsyncProclaimAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { auto leader = new LeaderKey(); - leader->set_name(param.name); - leader->set_key(param.key); - leader->set_rev(param.revision); - leader->set_lease(param.lease_id); + leader->set_name(parameters.name); + leader->set_key(parameters.key); + leader->set_rev(parameters.revision); + leader->set_lease(parameters.lease_id); ProclaimRequest proclaim_request; proclaim_request.set_allocated_leader(leader); - proclaim_request.set_value(param.value); + proclaim_request.set_value(parameters.value); response_reader = parameters.election_stub->AsyncProclaim(&context, proclaim_request, &cq_); response_reader->Finish(&reply, &status, (void *)this); @@ -75,11 +76,11 @@ etcdv3::AsyncProclaimResponse etcdv3::AsyncProclaimAction::ParseResponse() } etcdv3::AsyncLeaderAction::AsyncLeaderAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { LeaderRequest leader_request; - leader_request.set_name(param.name); + leader_request.set_name(parameters.name); response_reader = parameters.election_stub->AsyncLeader(&context, leader_request, &cq_); response_reader->Finish(&reply, &status, (void *)this); @@ -100,89 +101,47 @@ etcdv3::AsyncLeaderResponse etcdv3::AsyncLeaderAction::ParseResponse() return leader_resp; } -etcdv3::AsyncObserveAction::AsyncObserveAction( - etcdv3::ActionParameters const ¶m, const bool once) - : etcdv3::Action(param), once(once) +etcdv3::AsyncObserveAction::AsyncObserveAction(etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { LeaderRequest leader_request; - leader_request.set_name(param.name); + leader_request.set_name(parameters.name); response_reader = parameters.election_stub->AsyncObserve(&context, leader_request, &cq_, (void *)etcdv3::ELECTION_OBSERVE_CREATE); void *got_tag; bool ok = false; if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)etcdv3::ELECTION_OBSERVE_CREATE) { - response_reader->Read(&reply, (void *)this); + // n.b.: leave the issue of `Read` to the `waitForResponse` } else { throw std::runtime_error("failed to create a observe connection"); } } -void etcdv3::AsyncObserveAction::waitForResponse() +void etcdv3::AsyncObserveAction::waitForResponse() { void* got_tag; bool ok = false; - while(cq_.Next(&got_tag, &ok)) - { - if (isCancelled.load()) { - break; - } - if(ok == false) - { - break; - } - if(got_tag == (void*)this) // read tag - { - auto resp = ParseResponse(); - if (resp.get_error_code() != 0) { - CancelObserve(); - break; - } - } - if(isCancelled.load()) { - break; - } - if (once) { - break; - } - response_reader->Read(&reply, (void *)this); + if (isCancelled.load()) { + status = grpc::Status::CANCELLED; + } + if (!status.ok()) { + return; } -} -void etcdv3::AsyncObserveAction::waitForResponse(std::function callback) -{ - void* got_tag; - bool ok = false; - - while(cq_.Next(&got_tag, &ok)) - { - if(ok == false) - { - break; + response_reader->Read(&reply, (void *)this); + if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void*)this) { + auto response = ParseResponse(); + if (response.get_error_code() == 0) { + // issue the next read + response_reader->Read(&reply, (void *)this); + } else { + this->CancelObserve(); } - if (isCancelled.load()) { - break; - } - if(got_tag == (void*)this) // read tag - { - auto resp = ParseResponse(); - auto duration = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now() - start_timepoint); - callback(etcd::Response(resp, duration)); - if (resp.get_error_code() != 0) { - CancelObserve(); - break; - } - start_timepoint = std::chrono::high_resolution_clock::now(); - } - if(isCancelled.load()) { - break; - } - if (once) { - break; - } - response_reader->Read(&reply, (void *)this); + } else { + this->CancelObserve(); + status = grpc::Status::CANCELLED; } } @@ -223,14 +182,14 @@ etcdv3::AsyncObserveResponse etcdv3::AsyncObserveAction::ParseResponse() } etcdv3::AsyncResignAction::AsyncResignAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { auto leader = new LeaderKey(); - leader->set_name(param.name); - leader->set_key(param.key); - leader->set_rev(param.revision); - leader->set_lease(param.lease_id); + leader->set_name(parameters.name); + leader->set_key(parameters.key); + leader->set_rev(parameters.revision); + leader->set_lease(parameters.lease_id); ResignRequest resign_request; resign_request.set_allocated_leader(leader); diff --git a/src/v3/AsyncHeadAction.cpp b/src/v3/AsyncHeadAction.cpp index 275db70..df99024 100644 --- a/src/v3/AsyncHeadAction.cpp +++ b/src/v3/AsyncHeadAction.cpp @@ -7,8 +7,8 @@ using etcdserverpb::RangeRequest; etcdv3::AsyncHeadAction::AsyncHeadAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { RangeRequest get_request; get_request.set_key(etcdv3::NUL); diff --git a/src/v3/AsyncLeaseAction.cpp b/src/v3/AsyncLeaseAction.cpp index 5c9925f..4b2f2d0 100644 --- a/src/v3/AsyncLeaseAction.cpp +++ b/src/v3/AsyncLeaseAction.cpp @@ -10,8 +10,8 @@ using etcdserverpb::LeaseTimeToLiveRequest; using etcdserverpb::LeaseLeasesRequest; etcdv3::AsyncLeaseGrantAction::AsyncLeaseGrantAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { LeaseGrantRequest leasegrant_request; leasegrant_request.set_ttl(parameters.ttl); @@ -37,8 +37,8 @@ etcdv3::AsyncLeaseGrantResponse etcdv3::AsyncLeaseGrantAction::ParseResponse() } etcdv3::AsyncLeaseRevokeAction::AsyncLeaseRevokeAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { LeaseRevokeRequest leaserevoke_request; leaserevoke_request.set_id(parameters.lease_id); @@ -62,8 +62,8 @@ etcdv3::AsyncLeaseRevokeResponse etcdv3::AsyncLeaseRevokeAction::ParseResponse() } etcdv3::AsyncLeaseKeepAliveAction::AsyncLeaseKeepAliveAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { isCancelled = false; stream = parameters.lease_stub->AsyncLeaseKeepAlive(&context, &cq_, (void*)etcdv3::KEEPALIVE_CREATE); @@ -159,8 +159,8 @@ bool etcdv3::AsyncLeaseKeepAliveAction::Cancelled() const } etcdv3::AsyncLeaseTimeToLiveAction::AsyncLeaseTimeToLiveAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { LeaseTimeToLiveRequest leasetimetolive_request; leasetimetolive_request.set_id(parameters.lease_id); @@ -186,8 +186,8 @@ etcdv3::AsyncLeaseTimeToLiveResponse etcdv3::AsyncLeaseTimeToLiveAction::ParseRe } etcdv3::AsyncLeaseLeasesAction::AsyncLeaseLeasesAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { LeaseLeasesRequest leaseleases_request; diff --git a/src/v3/AsyncLockAction.cpp b/src/v3/AsyncLockAction.cpp index 0bb649f..cc3f56a 100644 --- a/src/v3/AsyncLockAction.cpp +++ b/src/v3/AsyncLockAction.cpp @@ -5,8 +5,8 @@ using v3lockpb::LockRequest; using v3lockpb::UnlockRequest; etcdv3::AsyncLockAction::AsyncLockAction( - ActionParameters const ¶m) - : etcdv3::Action(param) + ActionParameters && params) + : etcdv3::Action(std::move(params)) { LockRequest lock_request; lock_request.set_name(parameters.key); @@ -35,8 +35,8 @@ etcdv3::AsyncLockResponse etcdv3::AsyncLockAction::ParseResponse() } etcdv3::AsyncUnlockAction::AsyncUnlockAction( - ActionParameters const ¶m) - : etcdv3::Action(param) + ActionParameters && params) + : etcdv3::Action(std::move(params)) { UnlockRequest unlock_request; unlock_request.set_key(parameters.key); diff --git a/src/v3/AsyncPutAction.cpp b/src/v3/AsyncPutAction.cpp index 4bcadce..1e91dce 100644 --- a/src/v3/AsyncPutAction.cpp +++ b/src/v3/AsyncPutAction.cpp @@ -4,8 +4,8 @@ using etcdserverpb::PutRequest; etcdv3::AsyncPutAction::AsyncPutAction( - ActionParameters const ¶m) - : etcdv3::Action(param) + ActionParameters && params) + : etcdv3::Action(std::move(params)) { PutRequest put_request; put_request.set_key(parameters.key); diff --git a/src/v3/AsyncRangeAction.cpp b/src/v3/AsyncRangeAction.cpp index ca3449b..123c36b 100644 --- a/src/v3/AsyncRangeAction.cpp +++ b/src/v3/AsyncRangeAction.cpp @@ -7,8 +7,8 @@ using etcdserverpb::RangeRequest; etcdv3::AsyncRangeAction::AsyncRangeAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { RangeRequest get_request; if (parameters.key.empty()) { @@ -16,7 +16,7 @@ etcdv3::AsyncRangeAction::AsyncRangeAction( } else { get_request.set_key(parameters.key); } - get_request.set_limit(param.limit); + get_request.set_limit(parameters.limit); if(parameters.withPrefix) { if (parameters.key.empty()) { diff --git a/src/v3/AsyncSetAction.cpp b/src/v3/AsyncSetAction.cpp index 468dfe0..9ae2c29 100644 --- a/src/v3/AsyncSetAction.cpp +++ b/src/v3/AsyncSetAction.cpp @@ -4,8 +4,8 @@ #include "etcd/v3/Transaction.hpp" etcdv3::AsyncSetAction::AsyncSetAction( - etcdv3::ActionParameters const ¶m, bool create) - : etcdv3::Action(param) + etcdv3::ActionParameters && params, bool create) + : etcdv3::Action(std::move(params)) { etcdv3::Transaction transaction(parameters.key); isCreate = create; diff --git a/src/v3/AsyncTxnAction.cpp b/src/v3/AsyncTxnAction.cpp index 3e3fd88..ef1b116 100644 --- a/src/v3/AsyncTxnAction.cpp +++ b/src/v3/AsyncTxnAction.cpp @@ -4,8 +4,8 @@ etcdv3::AsyncTxnAction::AsyncTxnAction( - etcdv3::ActionParameters const ¶m, etcdv3::Transaction const &tx) - : etcdv3::Action(param) + etcdv3::ActionParameters && params, etcdv3::Transaction const &tx) + : etcdv3::Action(std::move(params)) { response_reader = parameters.kv_stub->AsyncTxn(&context, *tx.txn_request, &cq_); response_reader->Finish(&reply, &status, (void *)this); diff --git a/src/v3/AsyncUpdateAction.cpp b/src/v3/AsyncUpdateAction.cpp index d9f3587..587b3fe 100644 --- a/src/v3/AsyncUpdateAction.cpp +++ b/src/v3/AsyncUpdateAction.cpp @@ -11,8 +11,8 @@ using etcdserverpb::ResponseOp; using etcdserverpb::TxnRequest; etcdv3::AsyncUpdateAction::AsyncUpdateAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { etcdv3::Transaction transaction(parameters.key); transaction.init_compare(CompareResult::GREATER, diff --git a/src/v3/AsyncWatchAction.cpp b/src/v3/AsyncWatchAction.cpp index 78f9977..597dc68 100644 --- a/src/v3/AsyncWatchAction.cpp +++ b/src/v3/AsyncWatchAction.cpp @@ -7,8 +7,8 @@ using etcdserverpb::RangeResponse; using etcdserverpb::WatchCreateRequest; etcdv3::AsyncWatchAction::AsyncWatchAction( - etcdv3::ActionParameters const ¶m) - : etcdv3::Action(param) + etcdv3::ActionParameters && params) + : etcdv3::Action(std::move(params)) { isCancelled.store(false); stream = parameters.watch_stub->AsyncWatch(&context,&cq_,(void*)etcdv3::WATCH_CREATE); diff --git a/src/v3/action_constants.cpp b/src/v3/action_constants.cpp index cfc56ae..2baa6cf 100644 --- a/src/v3/action_constants.cpp +++ b/src/v3/action_constants.cpp @@ -43,3 +43,4 @@ char const * etcdv3::ELECTION_OBSERVE_CREATE = "observe create"; const int etcdv3::ERROR_KEY_NOT_FOUND = 100; const int etcdv3::ERROR_COMPARE_FAILED = 101; const int etcdv3::ERROR_KEY_ALREADY_EXISTS = 105; +const int etcdv3::ERROR_ACTION_CANCELLED = 106; diff --git a/tst/EtcdTest.cpp b/tst/EtcdTest.cpp index 94b42a2..13e483e 100644 --- a/tst/EtcdTest.cpp +++ b/tst/EtcdTest.cpp @@ -120,11 +120,6 @@ TEST_CASE("delete a value") int64_t modify_index = etcd.get("/test/key1").get().value().modified_index(); int64_t version = etcd.get("/test/key1").get().value().version(); - std::cerr << "index = " << index - << ", create index = " << create_index - << ", modify index = " << modify_index - << std::endl; - int head_index = etcd.head().get().index(); CHECK(index == head_index); @@ -174,7 +169,6 @@ TEST_CASE("atomic compare-and-delete based on prevIndex") CHECK("42" == res.prev_value().as_string()); } - TEST_CASE("deep atomic compare-and-swap") { etcd::Client etcd("http://127.0.0.1:2379"); diff --git a/tst/LockTest.cpp b/tst/LockTest.cpp index cef8ef8..f0a0fcf 100644 --- a/tst/LockTest.cpp +++ b/tst/LockTest.cpp @@ -61,11 +61,12 @@ TEST_CASE("double lock will fail") REQUIRE(0 == resp3.error_code()); // create a duration - first_lock_release = true; // using a duration longer than default lease TTL for lock (see: DEFAULT_LEASE_TTL_FOR_LOCK) std::this_thread::sleep_for(std::chrono::seconds(15)); // unlock the first lock + first_lock_release = true; + etcd::Response resp4 = etcd.unlock(lock_key).get(); CHECK("unlock" == resp4.action()); REQUIRE(resp4.is_ok()); @@ -171,18 +172,18 @@ TEST_CASE("concurrent lock & unlock") etcd::Client etcd("http://127.0.0.1:2379"); std::string const lock_key = "/test/test_key"; - constexpr size_t trials = 128; + constexpr size_t trials = 192; std::function locker = [&etcd](std::string const &key, const size_t index) { - std::cout << "start lock for " << index << std::endl; auto resp = etcd.lock(key).get(); - std::cout << "lock for " << index << " is ok, start sleep: ..." << resp.error_message() << std::endl; + std::cout << "lock for " << index << " is ok, starts sleeping: ..." << resp.error_message() << std::endl << std::flush; REQUIRE(resp.is_ok()); std::srand(index); size_t time_to_sleep = 1; std::this_thread::sleep_for(std::chrono::seconds(time_to_sleep)); + std::cout << "lock for " << index << " resumes from sleep: ..." << resp.error_message() << std::endl << std::flush; REQUIRE(etcd.unlock(resp.lock_key()).get().is_ok()); - std::cout << "thread " << index << " been unlocked" << std::endl; + std::cout << "thread " << index << " been unlocked" << std::endl << std::flush; }; std::vector locks(trials); diff --git a/tst/RewatchTest.cpp b/tst/RewatchTest.cpp index 93e9a9c..5f91ad6 100644 --- a/tst/RewatchTest.cpp +++ b/tst/RewatchTest.cpp @@ -4,8 +4,9 @@ #include #include -#include "etcd/Watcher.hpp" +#include "etcd/Client.hpp" #include "etcd/SyncClient.hpp" +#include "etcd/Watcher.hpp" static std::string etcd_uri("http://127.0.0.1:2379"); static int watcher_called = 0;