From 5994b54c6875851db1f5c7f519324bf277b2452a Mon Sep 17 00:00:00 2001 From: holobay Date: Tue, 1 Apr 2025 23:16:01 +0300 Subject: [PATCH] Bump up the version to v0.15.5 --- .github/workflows/build-test.yml | 20 +++++ CMakeLists.txt | 6 +- README.md | 4 +- etcd/Client.hpp | 19 +++++ etcd/Response.hpp | 9 ++ etcd/SyncClient.hpp | 26 ++++++ etcd/Value.hpp | 11 ++- etcd/v3/Action.hpp | 8 ++ etcd/v3/AsyncGRPC.hpp | 56 +++++++++++++ etcd/v3/Member.hpp | 33 ++++++++ etcd/v3/V3Response.hpp | 4 + etcd/v3/action_constants.hpp | 4 + src/Client.cpp | 21 +++++ src/Response.cpp | 7 ++ src/SyncClient.cpp | 80 +++++++++++++++++- src/Value.cpp | 41 ++++++++++ src/v3/Action.cpp | 9 ++ src/v3/AsyncGRPC.cpp | 116 +++++++++++++++++++++++++- src/v3/Member.cpp | 40 +++++++++ src/v3/V3Response.cpp | 5 ++ src/v3/action_constants.cpp | 4 + tst/EtcdMemberTest.cpp | 136 +++++++++++++++++++++++++++++++ tst/EtcdResolverTest.cpp | 7 ++ tst/EtcdSyncTest.cpp | 2 +- tst/WatcherTest.cpp | 35 ++++++++ 25 files changed, 693 insertions(+), 10 deletions(-) create mode 100644 etcd/v3/Member.hpp create mode 100644 src/v3/Member.cpp create mode 100644 tst/EtcdMemberTest.cpp diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 3f4d297..4022e57 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -403,6 +403,26 @@ jobs: killall -TERM etcd sleep 5 + - name: Etcd Member test + run: | + killall -TERM etcd || true + sleep 5 + + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib/x86_64-linux-gnu + + # use etcd v3 api + export ETCDCTL_API="3" + + + rm -rf default.etcd + /usr/local/bin/etcd & + sleep 5 + + ./build/bin/EtcdMemberTest + + killall -TERM etcd + sleep 5 + - name: Check ccache run: | ccache --show-stats diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ae75e7..8993733 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(etcd-cpp-api_VERSION_MAJOR 0) set(etcd-cpp-api_VERSION_MINOR 15) -set(etcd-cpp-api_VERSION_PATCH 4) +set(etcd-cpp-api_VERSION_PATCH 5) set(etcd-cpp-api_VERSION ${etcd-cpp-api_VERSION_MAJOR}.${etcd-cpp-api_VERSION_MINOR}.${etcd-cpp-api_VERSION_PATCH}) set(CMAKE_PROJECT_HOMEPAGE_URL https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3) @@ -90,7 +90,7 @@ macro(use_cxx target) endmacro(use_cxx) macro(set_exceptions target) - if(BUILD_NO_EXCEPTIONS) + if(BUILD_WITH_NO_EXCEPTIONS) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(${target} PRIVATE "-fno-exceptions") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") @@ -252,6 +252,7 @@ if(NOT BUILD_ETCD_CORE_ONLY) endif() install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/action_constants.hpp ${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/Transaction.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/Member.hpp DESTINATION include/etcd/v3) configure_file(etcd-cpp-api-config.in.cmake @@ -337,6 +338,7 @@ set(CPACK_DEBIAN_PACKAGE_INSTALL "/usr/lib/lib*.so*" "/usr/include/etcd/*.hpp" "/usr/include/etcd/v3/action_constants.hpp" "/usr/include/etcd/v3/Transaction.hpp" + "/usr/include/etcd/v3/Member.hpp" ) set(CPACK_DEBIAN_PACKAGE_BUILD_NUMBER_PREFIX "") diff --git a/README.md b/README.md index ef12993..d107c48 100644 --- a/README.md +++ b/README.md @@ -534,10 +534,10 @@ Values can be deleted with the `rm` method passing the key to be deleted as a pa should point to an existing value. There are conditional variations for deletion too. * `rm(std::string const& key)` unconditionally deletes the given key -* `rm_if(key, value, old_value)` deletes an already existing value but only if the previous +* `rm_if(key, old_value)` deletes an already existing value but only if the previous value equals with old_value. If the values does not match returns with "Compare failed" error (code `ERROR_COMPARE_FAILED`) -* `rm_if(key, value, old_index)` deletes an already existing value but only if the index of +* `rm_if(key, old_index)` deletes an already existing value but only if the index of the previous value equals with old_index. If the indices does not match returns with "Compare failed" error (code `ERROR_COMPARE_FAILED`) diff --git a/etcd/Client.hpp b/etcd/Client.hpp index 4f8a566..bea38fe 100644 --- a/etcd/Client.hpp +++ b/etcd/Client.hpp @@ -630,6 +630,25 @@ class Client { */ pplx::task leases(); + /** + * Add an etcd member to the etcd cluster, equivalent to `etcdctl member add`. + * @param peer_urls is comma separated list of URLs for the new member. + * @param is_learner is true if the added member is a learner. + */ + pplx::task add_member(std::string const& peer_urls, + bool is_learner); + + /** + * List all members, equivalent to `etcdctl member list`. + */ + pplx::task list_member(); + + /** + * Add an etcd member to the etcd cluster, equivalent to `etcdctl member add`. + * @param member_id is the ID of the member to be removed. + */ + pplx::task remove_member(const uint64_t member_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 diff --git a/etcd/Response.hpp b/etcd/Response.hpp index 25426a3..a4c879f 100644 --- a/etcd/Response.hpp +++ b/etcd/Response.hpp @@ -9,6 +9,7 @@ #include #include "etcd/Value.hpp" +#include "etcd/v3/Member.hpp" namespace etcdv3 { class AsyncWatchAction; @@ -208,6 +209,11 @@ class Response { */ std::vector const& leases() const; + /** + * Returns the member list. + */ + std::vector const& members() const; + protected: Response(const etcdv3::V3Response& response, std::chrono::microseconds const& duration); @@ -239,6 +245,9 @@ class Response { // for lease list std::vector _leases; + // for member list + std::vector _members; + friend class Client; friend class SyncClient; friend class KeepAlive; diff --git a/etcd/SyncClient.hpp b/etcd/SyncClient.hpp index 6859096..16c59c1 100644 --- a/etcd/SyncClient.hpp +++ b/etcd/SyncClient.hpp @@ -29,6 +29,9 @@ class AsyncLeaseKeepAliveAction; class AsyncLeaseTimeToLiveAction; class AsyncLeaseLeasesAction; class AsyncLockAction; +class AsyncAddMemberAction; +class AsyncListMemberAction; +class AsyncRemoveMemberAction; class AsyncUnlockAction; class AsyncPutAction; class AsyncRangeAction; @@ -679,6 +682,24 @@ class SyncClient { */ Response leases(); + /** + * Add an etcd member to the etcd cluster, equivalent to `etcdctl member add`. + * @param peer_urls is comma separated list of URLs for the new member. + * @param is_learner is true if the added member is a learner. + */ + Response add_member(std::string const& peer_urls, bool is_learner); + + /** + * List all members, equivalent to `etcdctl member list`. + */ + Response list_member(); + + /** + * Remove a member of an etcd cluster, equivalent to `etcdctl member remove`. + * @param member_id is the id of the member to be removed. + */ + Response remove_member(uint64_t member_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 @@ -835,6 +856,11 @@ class SyncClient { std::shared_ptr leasetimetolive_internal( int64_t lease_id); std::shared_ptr leases_internal(); + std::shared_ptr add_member_internal( + std::string const& peer_urls, bool is_learner); + std::shared_ptr list_member_internal(); + std::shared_ptr remove_member_internal( + const uint64_t member_id); Response lock_internal(std::string const& key, std::shared_ptr const& keepalive); std::shared_ptr lock_with_lease_internal( diff --git a/etcd/Value.hpp b/etcd/Value.hpp index 46f684b..5cc296c 100644 --- a/etcd/Value.hpp +++ b/etcd/Value.hpp @@ -91,7 +91,9 @@ class Value { int64_t leaseId; }; -typedef std::vector Values; +using Values = std::vector; + +std::ostream& operator<<(std::ostream& os, const Value& value); class Event { public: @@ -122,7 +124,12 @@ class Event { bool _has_kv, _has_prev_kv; }; -typedef std::vector Events; +using Events = std::vector; + +std::ostream& operator<<(std::ostream& os, const Event::EventType& value); + +std::ostream& operator<<(std::ostream& os, const Event& event); + } // namespace etcd #endif diff --git a/etcd/v3/Action.hpp b/etcd/v3/Action.hpp index 3f99a26..3104d50 100644 --- a/etcd/v3/Action.hpp +++ b/etcd/v3/Action.hpp @@ -15,6 +15,7 @@ using grpc::ClientContext; using grpc::CompletionQueue; using grpc::Status; +using etcdserverpb::Cluster; using etcdserverpb::KV; using etcdserverpb::Lease; using etcdserverpb::Watch; @@ -44,9 +45,16 @@ struct ActionParameters { std::string value; std::string old_value; std::string auth_token; + + // for cluster management apis + std::vector peer_urls; + bool is_learner; + uint64_t member_id; + std::chrono::microseconds grpc_timeout = std::chrono::microseconds::zero(); KV::Stub* kv_stub; Watch::Stub* watch_stub; + Cluster::Stub* cluster_stub; Lease::Stub* lease_stub; Lock::Stub* lock_stub; Election::Stub* election_stub; diff --git a/etcd/v3/AsyncGRPC.hpp b/etcd/v3/AsyncGRPC.hpp index 998937c..515007a 100644 --- a/etcd/v3/AsyncGRPC.hpp +++ b/etcd/v3/AsyncGRPC.hpp @@ -37,6 +37,12 @@ using etcdserverpb::LeaseRevokeRequest; using etcdserverpb::LeaseRevokeResponse; using etcdserverpb::LeaseTimeToLiveRequest; using etcdserverpb::LeaseTimeToLiveResponse; +using etcdserverpb::MemberAddRequest; +using etcdserverpb::MemberAddResponse; +using etcdserverpb::MemberListRequest; +using etcdserverpb::MemberListResponse; +using etcdserverpb::MemberRemoveRequest; +using etcdserverpb::MemberRemoveResponse; using etcdserverpb::PutRequest; using etcdserverpb::PutResponse; using etcdserverpb::RangeRequest; @@ -103,6 +109,24 @@ class AsyncLeaseKeepAliveResponse : public etcdv3::V3Response { void ParseResponse(LeaseKeepAliveResponse& resp); }; +class AsyncMemberAddResponse : public etcdv3::V3Response { + public: + AsyncMemberAddResponse(){}; + void ParseResponse(MemberAddResponse& resp); +}; + +class AsyncMemberListResponse : public etcdv3::V3Response { + public: + AsyncMemberListResponse(){}; + void ParseResponse(MemberListResponse& resp); +}; + +class AsyncMemberRemoveResponse : public etcdv3::V3Response { + public: + AsyncMemberRemoveResponse(){}; + void ParseResponse(MemberRemoveResponse& resp); +}; + class AsyncLeaseLeasesResponse : public etcdv3::V3Response { public: AsyncLeaseLeasesResponse(){}; @@ -275,6 +299,38 @@ class AsyncLeaseKeepAliveAction : public etcdv3::Action { friend class etcd::KeepAlive; }; +class AsyncAddMemberAction : public etcdv3::Action { + public: + AsyncAddMemberAction(etcdv3::ActionParameters&& params); + AsyncMemberAddResponse ParseResponse(); + + private: + MemberAddResponse reply; + std::unique_ptr> response_reader; +}; + +class AsyncListMemberAction : public etcdv3::Action { + public: + AsyncListMemberAction(etcdv3::ActionParameters&& params); + AsyncMemberListResponse ParseResponse(); + + private: + MemberListResponse reply; + std::unique_ptr> + response_reader; +}; + +class AsyncRemoveMemberAction : public etcdv3::Action { + public: + AsyncRemoveMemberAction(etcdv3::ActionParameters&& params); + AsyncMemberRemoveResponse ParseResponse(); + + private: + MemberRemoveResponse reply; + std::unique_ptr> + response_reader; +}; + class AsyncLeaseLeasesAction : public etcdv3::Action { public: AsyncLeaseLeasesAction(etcdv3::ActionParameters&& params); diff --git a/etcd/v3/Member.hpp b/etcd/v3/Member.hpp new file mode 100644 index 0000000..5260d21 --- /dev/null +++ b/etcd/v3/Member.hpp @@ -0,0 +1,33 @@ +#ifndef __V3_ETCDV3MEMBERS_HPP__ +#define __V3_ETCDV3MEMBERS_HPP__ + +#include +#include +#include +using namespace std; + +namespace etcdv3 { +class Member { + public: + Member(); + + void set_id(uint64_t const& id); + uint64_t const& get_id() const; + void set_name(std::string const& name); + std::string const& get_name() const; + void set_peerURLs(std::vector const& peerURLs); + std::vector const& get_peerURLs() const; + void set_clientURLs(std::vector const& clientURLs); + std::vector const& get_clientURLs() const; + void set_learner(bool isLearner); + bool get_learner() const; + + private: + uint64_t id; + std::string name; + std::vector peerURLs; + std::vector clientURLs; + bool isLearner; +}; +} // namespace etcdv3 +#endif diff --git a/etcd/v3/V3Response.hpp b/etcd/v3/V3Response.hpp index c5042b7..5d807f0 100644 --- a/etcd/v3/V3Response.hpp +++ b/etcd/v3/V3Response.hpp @@ -6,6 +6,7 @@ #include "proto/v3election.pb.h" #include "etcd/v3/KeyValue.hpp" +#include "etcd/v3/Member.hpp" namespace etcdv3 { class V3Response { @@ -36,6 +37,7 @@ class V3Response { uint64_t get_member_id() const; uint64_t get_raft_term() const; std::vector const& get_leases() const; + std::vector const& get_members() const; protected: int error_code; @@ -59,6 +61,8 @@ class V3Response { // for lease list std::vector leases; + // for member list + std::vector members; }; } // namespace etcdv3 #endif diff --git a/etcd/v3/action_constants.hpp b/etcd/v3/action_constants.hpp index e39132e..605d84b 100644 --- a/etcd/v3/action_constants.hpp +++ b/etcd/v3/action_constants.hpp @@ -23,6 +23,10 @@ extern char const* LEASEKEEPALIVE; extern char const* LEASETIMETOLIVE; extern char const* LEASELEASES; +extern char const* ADDMEMBER; +extern char const* LISTMEMBER; +extern char const* REMOVEMEMBER; + extern char const* CAMPAIGN_ACTION; extern char const* PROCLAIM_ACTION; extern char const* LEADER_ACTION; diff --git a/src/Client.cpp b/src/Client.cpp index 0bab966..bf1ea0c 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -494,6 +494,27 @@ pplx::task etcd::Client::leases() { this->client->leases_internal()); } +pplx::task etcd::Client::add_member( + std::string const& peer_urls, bool is_learner) { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->add_member_internal(peer_urls, is_learner)); +} + +pplx::task etcd::Client::list_member() { + return etcd::detail::asyncify( + static_cast>(Response::create), + this->client->list_member_internal()); +} + +pplx::task etcd::Client::remove_member( + const uint64_t member_id) { + return etcd::detail::asyncify( + static_cast>( + Response::create), + this->client->remove_member_internal(member_id)); +} + pplx::task etcd::Client::lock(std::string const& key) { static const int DEFAULT_LEASE_TTL_FOR_LOCK = 10; // see also etcd::SyncClient::lock diff --git a/src/Response.cpp b/src/Response.cpp index 6a6a3d6..6d61586 100644 --- a/src/Response.cpp +++ b/src/Response.cpp @@ -25,6 +25,7 @@ etcd::Response::Response(const etcd::Response& response) { this->_raft_term = response._raft_term; this->_leases = response._leases; + this->_members = response._members; } etcd::Response::Response(const etcdv3::V3Response& reply, @@ -64,6 +65,8 @@ etcd::Response::Response(const etcdv3::V3Response& reply, // lease list this->_leases = reply.get_leases(); + // member list + this->_members = reply.get_members(); } etcd::Response::Response(int error_code, std::string const& error_message) @@ -131,3 +134,7 @@ uint64_t etcd::Response::raft_term() const { return this->_raft_term; } std::vector const& etcd::Response::leases() const { return this->_leases; } + +std::vector const& etcd::Response::members() const { + return this->_members; +} diff --git a/src/SyncClient.cpp b/src/SyncClient.cpp index 05fcedb..679be12 100644 --- a/src/SyncClient.cpp +++ b/src/SyncClient.cpp @@ -8,6 +8,7 @@ #include #endif +#include #include #include #include @@ -101,6 +102,7 @@ static std::string string_join(std::vector const& srcs, static bool dns_resolve(std::string const& target, std::vector& endpoints, bool ipv4 = true) { std::vector target_parts; + bool ipv6_url{false}; { size_t rindex = target.rfind(':'); if (rindex == target.npos) { @@ -110,7 +112,17 @@ static bool dns_resolve(std::string const& target, #endif return false; } - target_parts.push_back(target.substr(0, rindex)); + + std::string host(target.substr(0, rindex)); + + // host format is [ipv6] + if (!ipv4 && !host.empty() && host[0] == '[' && + host[host.size() - 1] == ']') { + host = target.substr(1, rindex - 2); + ipv6_url = true; + } + + target_parts.push_back(host); target_parts.push_back(target.substr(rindex + 1)); } @@ -132,6 +144,16 @@ static bool dns_resolve(std::string const& target, } #endif + if (ipv6_url) { + // check valid ipv6 + struct sockaddr_in6 sa6; + if (inet_pton(AF_INET6, target_parts[0].c_str(), &(sa6.sin6_addr)) == 1) { + endpoints.emplace_back(target); + return true; + } + return false; + } + struct addrinfo hints = {}, *addrs; hints.ai_family = ipv4 ? AF_INET : AF_INET6; hints.ai_socktype = SOCK_STREAM; @@ -342,6 +364,7 @@ void etcd::SyncClient::TokenAuthenticatorDeleter::operator()( struct etcd::SyncClient::EtcdServerStubs { std::unique_ptr kvServiceStub; std::unique_ptr watchServiceStub; + std::unique_ptr clusterServiceStub; std::unique_ptr leaseServiceStub; std::unique_ptr lockServiceStub; std::unique_ptr electionServiceStub; @@ -370,6 +393,7 @@ etcd::SyncClient::SyncClient(std::string const& address, stubs.reset(new EtcdServerStubs{}); stubs->kvServiceStub = KV::NewStub(this->channel); stubs->watchServiceStub = Watch::NewStub(this->channel); + stubs->clusterServiceStub = Cluster::NewStub(this->channel); stubs->leaseServiceStub = Lease::NewStub(this->channel); stubs->lockServiceStub = Lock::NewStub(this->channel); stubs->electionServiceStub = Election::NewStub(this->channel); @@ -995,6 +1019,60 @@ etcd::SyncClient::leases_internal() { return std::make_shared(std::move(params)); } +etcd::Response etcd::SyncClient::add_member(std::string const& peer_urls, + bool is_learner) { + return Response::create(this->add_member_internal(peer_urls, is_learner)); +} + +std::shared_ptr +etcd::SyncClient::add_member_internal(std::string const& peer_urls, + bool is_learner) { + etcdv3::ActionParameters params; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.grpc_timeout = this->grpc_timeout; + params.cluster_stub = stubs->clusterServiceStub.get(); + + std::vector peer_urls_vector; + std::istringstream iss(peer_urls); + std::string peer_url; + while (std::getline(iss, peer_url, ',')) { + peer_urls_vector.push_back(peer_url); + } + + params.is_learner = is_learner; + params.peer_urls = peer_urls_vector; + + return std::make_shared(std::move(params)); +} + +etcd::Response etcd::SyncClient::list_member() { + return Response::create(this->list_member_internal()); +} + +std::shared_ptr +etcd::SyncClient::list_member_internal() { + etcdv3::ActionParameters params; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.grpc_timeout = this->grpc_timeout; + params.cluster_stub = stubs->clusterServiceStub.get(); + return std::make_shared(std::move(params)); +} + +etcd::Response etcd::SyncClient::remove_member(const uint64_t member_id) { + return Response::create(this->remove_member_internal(member_id)); +} + +std::shared_ptr +etcd::SyncClient::remove_member_internal(const uint64_t member_id) { + etcdv3::ActionParameters params; + params.auth_token.assign(this->token_authenticator->renew_if_expired()); + params.grpc_timeout = this->grpc_timeout; + params.cluster_stub = stubs->clusterServiceStub.get(); + params.member_id = member_id; + + 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. // diff --git a/src/Value.cpp b/src/Value.cpp index 64bf1cd..c09e32b 100644 --- a/src/Value.cpp +++ b/src/Value.cpp @@ -1,3 +1,4 @@ +#include #include #include "etcd/Value.hpp" @@ -44,6 +45,19 @@ int etcd::Value::ttl() const { return _ttl; } int64_t etcd::Value::lease() const { return leaseId; } +std::ostream& etcd::operator<<(std::ostream& os, const etcd::Value& value) { + os << "Event: {"; + os << "Key: " << value.key() << ", "; + os << "Value: " << value.as_string() << ", "; + os << "Created: " << value.created_index() << ", "; + os << "Modified: " << value.modified_index() << ", "; + os << "Version: " << value.version() << ", "; + os << "TTL: " << value.ttl() << ", "; + os << "Lease: " << value.lease() << ", "; + os << "}"; + return os; +} + etcd::Event::Event(mvccpb::Event const& event) { _has_kv = event.has_kv(); _has_prev_kv = event.has_prev_kv(); @@ -73,3 +87,30 @@ bool etcd::Event::has_prev_kv() const { return _has_prev_kv; } const etcd::Value& etcd::Event::kv() const { return _kv; } const etcd::Value& etcd::Event::prev_kv() const { return _prev_kv; } + +std::ostream& etcd::operator<<(std::ostream& os, + const etcd::Event::EventType& value) { + switch (value) { + case etcd::Event::EventType::PUT: + os << "PUT"; + break; + case etcd::Event::EventType::DELETE_: + os << "DELETE"; + break; + case etcd::Event::EventType::INVALID: + os << "INVALID"; + break; + } + return os; +} + +std::ostream& etcd::operator<<(std::ostream& os, const etcd::Event& event) { + os << "Event type: " << event.event_type(); + if (event.has_kv()) { + os << ", KV: " << event.kv(); + } + if (event.has_prev_kv()) { + os << ", Prev KV: " << event.prev_kv(); + } + return os; +} diff --git a/src/v3/Action.cpp b/src/v3/Action.cpp index fff45e4..d64e259 100644 --- a/src/v3/Action.cpp +++ b/src/v3/Action.cpp @@ -2,6 +2,15 @@ #include #include #include "etcd/v3/action_constants.hpp" +#include + +#ifndef GPR_ASSERT +#define GPR_ASSERT(x) \ + if (!(x)) { \ + fprintf(stderr, "%s:%d assert failed\n", __FILE__, __LINE__); \ + abort(); \ +} +#endif etcdv3::Action::Action(etcdv3::ActionParameters const& params) { parameters = params; diff --git a/src/v3/AsyncGRPC.cpp b/src/v3/AsyncGRPC.cpp index bc1dcdd..5e17667 100644 --- a/src/v3/AsyncGRPC.cpp +++ b/src/v3/AsyncGRPC.cpp @@ -95,6 +95,43 @@ void etcdv3::AsyncLeaseKeepAliveResponse::ParseResponse( value.set_ttl(resp.ttl()); } +void etcdv3::AsyncMemberAddResponse::ParseResponse(MemberAddResponse& resp) { + index = resp.header().revision(); + std::string member_type = "Member"; + if (resp.member().islearner()) { + member_type = "Learner"; + } + std::cout << "Member (" << resp.member().id() << ")" + << " Added to the etcd cluster as " << member_type << std::endl; +} + +void etcdv3::AsyncMemberListResponse::ParseResponse(MemberListResponse& resp) { + index = resp.header().revision(); + for (auto member : resp.members()) { + etcdv3::Member m; + m.set_id(member.id()); + m.set_name(member.name()); + + std::vector clientUrlsVec, peerUrlsVec; + for (const auto& clientUrl : member.clienturls()) { + clientUrlsVec.push_back(clientUrl); + } + for (const auto& peerUrl : member.peerurls()) { + peerUrlsVec.push_back(peerUrl); + } + + m.set_clientURLs(clientUrlsVec); + m.set_peerURLs(peerUrlsVec); + + members.push_back(m); + } +} + +void etcdv3::AsyncMemberRemoveResponse::ParseResponse( + MemberRemoveResponse& resp) { + index = resp.header().revision(); +} + void etcdv3::AsyncLeaseLeasesResponse::ParseResponse( LeaseLeasesResponse& resp) { index = resp.header().revision(); @@ -667,6 +704,81 @@ etcdv3::AsyncLeaseKeepAliveAction::mutable_parameters() { return this->parameters; } +etcdv3::AsyncAddMemberAction::AsyncAddMemberAction( + etcdv3::ActionParameters&& params) + : etcdv3::Action(std::move(params)) { + MemberAddRequest add_member_request; + + for (const auto& url : parameters.peer_urls) { + add_member_request.add_peerurls(url); + } + add_member_request.set_islearner(parameters.is_learner); + response_reader = parameters.cluster_stub->AsyncMemberAdd( + &context, add_member_request, &cq_); + response_reader->Finish(&reply, &status, (void*) this); +} + +etcdv3::AsyncMemberAddResponse etcdv3::AsyncAddMemberAction::ParseResponse() { + AsyncMemberAddResponse add_member_resp; + add_member_resp.set_action(etcdv3::ADDMEMBER); + + if (!status.ok()) { + add_member_resp.set_error_code(status.error_code()); + add_member_resp.set_error_message(status.error_message()); + } else { + add_member_resp.ParseResponse(reply); + } + return add_member_resp; +} + +etcdv3::AsyncListMemberAction::AsyncListMemberAction( + etcdv3::ActionParameters&& params) + : etcdv3::Action(std::move(params)) { + MemberListRequest member_list_request; + + response_reader = parameters.cluster_stub->AsyncMemberList( + &context, member_list_request, &cq_); + response_reader->Finish(&reply, &status, (void*) this); +} + +etcdv3::AsyncMemberListResponse etcdv3::AsyncListMemberAction::ParseResponse() { + AsyncMemberListResponse list_member_resp; + list_member_resp.set_action(etcdv3::LISTMEMBER); + + if (!status.ok()) { + list_member_resp.set_error_code(status.error_code()); + list_member_resp.set_error_message(status.error_message()); + } else { + list_member_resp.ParseResponse(reply); + } + return list_member_resp; +} + +etcdv3::AsyncRemoveMemberAction::AsyncRemoveMemberAction( + etcdv3::ActionParameters&& params) + : etcdv3::Action(std::move(params)) { + MemberRemoveRequest remove_member_request; + + remove_member_request.set_id(parameters.member_id); + response_reader = parameters.cluster_stub->AsyncMemberRemove( + &context, remove_member_request, &cq_); + response_reader->Finish(&reply, &status, (void*) this); +} + +etcdv3::AsyncMemberRemoveResponse +etcdv3::AsyncRemoveMemberAction::ParseResponse() { + AsyncMemberRemoveResponse remove_member_resp; + remove_member_resp.set_action(etcdv3::REMOVEMEMBER); + + if (!status.ok()) { + remove_member_resp.set_error_code(status.error_code()); + remove_member_resp.set_error_message(status.error_message()); + } else { + remove_member_resp.ParseResponse(reply); + } + return remove_member_resp; +} + etcdv3::AsyncLeaseLeasesAction::AsyncLeaseLeasesAction( etcdv3::ActionParameters&& params) : etcdv3::Action(std::move(params)) { @@ -994,9 +1106,9 @@ etcdv3::AsyncSetAction::AsyncSetAction(etcdv3::ActionParameters&& params, // backwards compatibility txn.add_success_range(parameters.key); if (create) { - txn.add_failure_put(parameters.key, parameters.value, parameters.lease_id); - } else { txn.add_failure_range(parameters.key); + } else { + txn.add_failure_put(parameters.key, parameters.value, parameters.lease_id); } response_reader = parameters.kv_stub->AsyncTxn(&context, *txn.txn_request, &cq_); diff --git a/src/v3/Member.cpp b/src/v3/Member.cpp new file mode 100644 index 0000000..d01d50d --- /dev/null +++ b/src/v3/Member.cpp @@ -0,0 +1,40 @@ +#include "etcd/v3/Member.hpp" + +etcdv3::Member::Member() { + id = 0; + name = ""; + peerURLs = {}; + clientURLs = {}; + isLearner = false; +} + +void etcdv3::Member::set_id(uint64_t const& id) { this->id = id; } + +void etcdv3::Member::set_name(std::string const& name) { this->name = name; } + +void etcdv3::Member::set_peerURLs(std::vector const& peerURLs) { + this->peerURLs = peerURLs; +} + +void etcdv3::Member::set_clientURLs( + std::vector const& clientURLs) { + this->clientURLs = clientURLs; +} + +void etcdv3::Member::set_learner(bool isLearner) { + this->isLearner = isLearner; +} + +uint64_t const& etcdv3::Member::get_id() const { return id; } + +std::string const& etcdv3::Member::get_name() const { return name; } + +std::vector const& etcdv3::Member::get_peerURLs() const { + return peerURLs; +} + +std::vector const& etcdv3::Member::get_clientURLs() const { + return clientURLs; +} + +bool etcdv3::Member::get_learner() const { return isLearner; } diff --git a/src/v3/V3Response.cpp b/src/v3/V3Response.cpp index 21a235f..3f6e171 100644 --- a/src/v3/V3Response.cpp +++ b/src/v3/V3Response.cpp @@ -1,4 +1,5 @@ #include "etcd/v3/V3Response.hpp" +#include "etcd/v3/Member.hpp" #include "etcd/v3/action_constants.hpp" void etcdv3::V3Response::set_error_code(int code) { error_code = code; } @@ -79,3 +80,7 @@ uint64_t etcdv3::V3Response::get_raft_term() const { return this->raft_term; } std::vector const& etcdv3::V3Response::get_leases() const { return this->leases; } + +std::vector const& etcdv3::V3Response::get_members() const { + return this->members; +} diff --git a/src/v3/action_constants.cpp b/src/v3/action_constants.cpp index 8bac56c..8dee89f 100644 --- a/src/v3/action_constants.cpp +++ b/src/v3/action_constants.cpp @@ -19,6 +19,10 @@ char const* etcdv3::LEASEKEEPALIVE = "leasekeepalive"; char const* etcdv3::LEASETIMETOLIVE = "leasetimetolive"; char const* etcdv3::LEASELEASES = "leaseleases"; +char const* etcdv3::ADDMEMBER = "addmember"; +char const* etcdv3::LISTMEMBER = "listmember"; +char const* etcdv3::REMOVEMEMBER = "removemember"; + char const* etcdv3::CAMPAIGN_ACTION = "campaign"; char const* etcdv3::PROCLAIM_ACTION = "preclaim"; char const* etcdv3::LEADER_ACTION = "leader"; diff --git a/tst/EtcdMemberTest.cpp b/tst/EtcdMemberTest.cpp new file mode 100644 index 0000000..fcb5278 --- /dev/null +++ b/tst/EtcdMemberTest.cpp @@ -0,0 +1,136 @@ +#include +#define CATCH_CONFIG_MAIN +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "etcd/Client.hpp" + +static const std::string etcd_url = + etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379"); + +pid_t new_etcd_pid = 0; + +pid_t start_etcd_server(const std::string& etcd_path, + const std::vector& args) { + pid_t pid = fork(); + if (pid == -1) { + std::cout << "Failed to fork process" << std::endl; + exit(EXIT_FAILURE); + } else if (pid == 0) { + std::vector c_args; + c_args.push_back(const_cast(etcd_path.c_str())); + for (const auto& arg : args) { + c_args.push_back(const_cast(arg.c_str())); + } + c_args.push_back(nullptr); + + if (execvp(etcd_path.c_str(), c_args.data()) == -1) { + std::cout << "Failed to exec etcd process: " << std::strerror(errno) + << std::endl; + exit(EXIT_FAILURE); + } + } + + return pid; +} + +TEST_CASE("add member") { + etcd::Client etcd(etcd_url); + etcd::Response res = etcd.list_member().get(); + REQUIRE(res.is_ok()); + CHECK(1 == res.members().size()); + + std::string member_name = res.members()[0].get_name(); + std::string prev_peer_urls = res.members()[0].get_peerURLs()[0]; + + // Add a new member + std::string peer_urls = "http://127.0.0.1:33691"; + std::string client_urls = "http://127.0.0.1:33690"; + bool is_learner = false; + res = etcd.add_member(peer_urls, is_learner).get(); + REQUIRE(res.is_ok()); + + // Create the directory for the new etcd server + std::string cmd = "mkdir -p /tmp/new_etcd_member"; + system(cmd.c_str()); + + // Start a new etcd server + std::vector args = { + "--name", + "new_etcd_member", + "--initial-advertise-peer-urls", + peer_urls, + "--listen-peer-urls", + peer_urls, + "--initial-cluster", + member_name + "=" + prev_peer_urls + ",new_etcd_member=" + peer_urls, + "--initial-cluster-state", + "existing", + "--listen-client-urls", + client_urls, + "--advertise-client-urls", + client_urls, + "--data-dir", + "/tmp/new_etcd_member", + }; + + new_etcd_pid = start_etcd_server("/usr/local/bin/etcd", args); + + std::this_thread::sleep_for(std::chrono::seconds(30)); + + // Check the member's number + { + etcd::Response res = etcd.list_member().get(); + REQUIRE(res.is_ok()); + CHECK(2 == res.members().size()); + } +} + +TEST_CASE("member remove") { + etcd::Client etcd(etcd_url); + etcd::Response res = etcd.list_member().get(); + REQUIRE(res.is_ok()); + CHECK(2 == res.members().size()); + + uint64_t member_id = 0; + for (const auto& member : res.members()) { + if (member.get_name() == "new_etcd_member") { + member_id = member.get_id(); + break; + } + } + + REQUIRE(member_id != 0); + // Remove the new member + res = etcd.remove_member(member_id).get(); + + std::this_thread::sleep_for(std::chrono::seconds(30)); + + // Check whether the new etcd server is quited + { + int status; + pid_t wpid = waitpid(new_etcd_pid, &status, WNOHANG); + REQUIRE(wpid == new_etcd_pid); + REQUIRE(WIFEXITED(status)); + REQUIRE(WEXITSTATUS(status) == 0); + } + + // Remove the directory for the new etcd server + std::string cmd = "rm -rf /tmp/new_etcd_member"; + system(cmd.c_str()); + + // Check the member's number + { + etcd::Response res = etcd.list_member().get(); + REQUIRE(res.is_ok()); + CHECK(1 == res.members().size()); + } +} diff --git a/tst/EtcdResolverTest.cpp b/tst/EtcdResolverTest.cpp index 2d00bf6..14f7ad1 100644 --- a/tst/EtcdResolverTest.cpp +++ b/tst/EtcdResolverTest.cpp @@ -9,6 +9,9 @@ static const std::string etcd_v4_url = etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379"); static const std::string etcd_v6_url = etcdv3::detail::resolve_etcd_endpoints("http://::1:2379"); +// http://[ipv6]:port url +static const std::string etcd_ipv6_url = + etcdv3::detail::resolve_etcd_endpoints("http://[::1]:2379"); TEST_CASE("test ipv4 connection") { std::cout << "ipv4 endpoints: " << etcd_v4_url << std::endl; @@ -20,4 +23,8 @@ TEST_CASE("test ipv6 connection") { std::cout << "ipv6 endpoints: " << etcd_v6_url << std::endl; etcd::Client etcd(etcd_v6_url); REQUIRE(etcd.head().get().is_ok()); + + std::cout << "ipv6 endpoints: " << etcd_ipv6_url << std::endl; + etcd::Client etcd1(etcd_ipv6_url); + REQUIRE(etcd1.head().get().is_ok()); } diff --git a/tst/EtcdSyncTest.cpp b/tst/EtcdSyncTest.cpp index e588c28..a0ac32b 100644 --- a/tst/EtcdSyncTest.cpp +++ b/tst/EtcdSyncTest.cpp @@ -18,7 +18,7 @@ TEST_CASE("sync operations") { // add CHECK(0 == etcd.add("/test/key1", "42").error_code()); CHECK(etcd::ERROR_KEY_ALREADY_EXISTS == - etcd.add("/test/key1", "42").error_code()); // Key already exists + etcd.add("/test/key1", "41").error_code()); // Key already exists CHECK("42" == etcd.get("/test/key1").value().as_string()); // modify diff --git a/tst/WatcherTest.cpp b/tst/WatcherTest.cpp index c5fe100..43b05bc 100644 --- a/tst/WatcherTest.cpp +++ b/tst/WatcherTest.cpp @@ -191,6 +191,41 @@ TEST_CASE("create two watcher") { std::this_thread::sleep_for(std::chrono::seconds(5)); } +TEST_CASE("using two watcher") { + etcd::SyncClient etcd(etcd_url); + + int watched1 = 0; + int watched2 = 0; + + etcd::Watcher w1( + etcd, "/test/def", + [&](etcd::Response const& resp) { + std::cout << "w1 called: " << resp.events().at(0).event_type() << " on " + << resp.events().at(0).kv().key() << std::endl; + ++watched1; + }, + true); + etcd::Watcher w2( + etcd, "/test", + [&](etcd::Response const& resp) { + std::cout << "w2 called: " << resp.events().at(0).event_type() << " on " + << resp.events().at(0).kv().key() << std::endl; + ++watched2; + }, + true); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + + etcd.put("/test/def/xxx", "42"); + etcd.put("/test/abc", "42"); + etcd.rm("/test/def/xxx"); + etcd.rm("/test/abc"); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + CHECK(2 == watched1); + CHECK(4 == watched2); +} + // TEST_CASE("request cancellation") // { // etcd::Client etcd(etcd_url);