Add the cluster manager api. (#276)

As titled.

Signed-off-by: Ye Cao <caoye.cao@alibaba-inc.com>
This commit is contained in:
Ye Cao 2024-08-02 10:30:43 +08:00 committed by GitHub
parent 3a594890c9
commit 3ad17314d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 561 additions and 1 deletions

View File

@ -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

View File

@ -630,6 +630,25 @@ class Client {
*/
pplx::task<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.
*/
pplx::task<Response> add_member(std::string const& peer_urls,
bool is_learner);
/**
* List all members, equivalent to `etcdctl member list`.
*/
pplx::task<Response> 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<Response> 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

View File

@ -9,6 +9,8 @@
#include <vector>
#include "etcd/Value.hpp"
#include "etcd/v3/Member.hpp"
#include "rpc.pb.h"
namespace etcdv3 {
class AsyncWatchAction;
@ -208,6 +210,11 @@ class Response {
*/
std::vector<int64_t> const& leases() const;
/**
* Returns the member list.
*/
std::vector<etcdv3::Member> const& members() const;
protected:
Response(const etcdv3::V3Response& response,
std::chrono::microseconds const& duration);
@ -239,6 +246,9 @@ class Response {
// for lease list
std::vector<int64_t> _leases;
// for member list
std::vector<etcdv3::Member> _members;
friend class Client;
friend class SyncClient;
friend class KeepAlive;

View File

@ -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<etcdv3::AsyncLeaseTimeToLiveAction> leasetimetolive_internal(
int64_t lease_id);
std::shared_ptr<etcdv3::AsyncLeaseLeasesAction> leases_internal();
std::shared_ptr<etcdv3::AsyncAddMemberAction> add_member_internal(
std::string const& peer_urls, bool is_learner);
std::shared_ptr<etcdv3::AsyncListMemberAction> list_member_internal();
std::shared_ptr<etcdv3::AsyncRemoveMemberAction> remove_member_internal(
const uint64_t member_id);
Response lock_internal(std::string const& key,
std::shared_ptr<etcd::KeepAlive> const& keepalive);
std::shared_ptr<etcdv3::AsyncLockAction> lock_with_lease_internal(

View File

@ -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<std::string> 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;

View File

@ -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<ClientAsyncResponseReader<MemberAddResponse>> response_reader;
};
class AsyncListMemberAction : public etcdv3::Action {
public:
AsyncListMemberAction(etcdv3::ActionParameters&& params);
AsyncMemberListResponse ParseResponse();
private:
MemberListResponse reply;
std::unique_ptr<ClientAsyncResponseReader<MemberListResponse>>
response_reader;
};
class AsyncRemoveMemberAction : public etcdv3::Action {
public:
AsyncRemoveMemberAction(etcdv3::ActionParameters&& params);
AsyncMemberRemoveResponse ParseResponse();
private:
MemberRemoveResponse reply;
std::unique_ptr<ClientAsyncResponseReader<MemberRemoveResponse>>
response_reader;
};
class AsyncLeaseLeasesAction : public etcdv3::Action {
public:
AsyncLeaseLeasesAction(etcdv3::ActionParameters&& params);

32
etcd/v3/Member.hpp Normal file
View File

@ -0,0 +1,32 @@
#ifndef __V3_ETCDV3MEMBERS_HPP__
#define __V3_ETCDV3MEMBERS_HPP__
#include <string>
#include <vector>
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<std::string> const& peerURLs);
std::vector<std::string> const& get_peerURLs() const;
void set_clientURLs(std::vector<std::string> const& clientURLs);
std::vector<std::string> const& get_clientURLs() const;
void set_learner(bool isLearner);
bool get_learner() const;
private:
uint64_t id;
std::string name;
std::vector<std::string> peerURLs;
std::vector<std::string> clientURLs;
bool isLearner;
};
} // namespace etcdv3
#endif

View File

@ -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<int64_t> const& get_leases() const;
std::vector<etcdv3::Member> const& get_members() const;
protected:
int error_code;
@ -59,6 +61,8 @@ class V3Response {
// for lease list
std::vector<int64_t> leases;
// for member list
std::vector<etcdv3::Member> members;
};
} // namespace etcdv3
#endif

View File

@ -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;

View File

@ -494,6 +494,27 @@ pplx::task<etcd::Response> etcd::Client::leases() {
this->client->leases_internal());
}
pplx::task<etcd::Response> etcd::Client::add_member(
std::string const& peer_urls, bool is_learner) {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncAddMemberAction>>(Response::create),
this->client->add_member_internal(peer_urls, is_learner));
}
pplx::task<etcd::Response> etcd::Client::list_member() {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncListMemberAction>>(Response::create),
this->client->list_member_internal());
}
pplx::task<etcd::Response> etcd::Client::remove_member(
const uint64_t member_id) {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRemoveMemberAction>>(
Response::create),
this->client->remove_member_internal(member_id));
}
pplx::task<etcd::Response> etcd::Client::lock(std::string const& key) {
static const int DEFAULT_LEASE_TTL_FOR_LOCK =
10; // see also etcd::SyncClient::lock

View File

@ -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<int64_t> const& etcd::Response::leases() const {
return this->_leases;
}
std::vector<etcdv3::Member> const& etcd::Response::members() const {
return this->_members;
}

View File

@ -342,6 +342,7 @@ void etcd::SyncClient::TokenAuthenticatorDeleter::operator()(
struct etcd::SyncClient::EtcdServerStubs {
std::unique_ptr<etcdserverpb::KV::Stub> kvServiceStub;
std::unique_ptr<etcdserverpb::Watch::Stub> watchServiceStub;
std::unique_ptr<etcdserverpb::Cluster::Stub> clusterServiceStub;
std::unique_ptr<etcdserverpb::Lease::Stub> leaseServiceStub;
std::unique_ptr<v3lockpb::Lock::Stub> lockServiceStub;
std::unique_ptr<v3electionpb::Election::Stub> electionServiceStub;
@ -370,6 +371,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 +997,60 @@ etcd::SyncClient::leases_internal() {
return std::make_shared<etcdv3::AsyncLeaseLeasesAction>(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<etcdv3::AsyncAddMemberAction>
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<std::string> 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<etcdv3::AsyncAddMemberAction>(std::move(params));
}
etcd::Response etcd::SyncClient::list_member() {
return Response::create(this->list_member_internal());
}
std::shared_ptr<etcdv3::AsyncListMemberAction>
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<etcdv3::AsyncListMemberAction>(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<etcdv3::AsyncRemoveMemberAction>
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<etcdv3::AsyncRemoveMemberAction>(std::move(params));
}
etcd::Response etcd::SyncClient::lock(std::string const& key) {
// routines in lock usually will be fast, less than 10 seconds.
//

View File

@ -1,5 +1,5 @@
#include <iomanip>
#include <cstdint>
#include <iomanip>
#include "etcd/Value.hpp"
#include "etcd/v3/KeyValue.hpp"

View File

@ -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<std::string> 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)) {

40
src/v3/Member.cpp Normal file
View File

@ -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<std::string> const& peerURLs) {
this->peerURLs = peerURLs;
}
void etcdv3::Member::set_clientURLs(
std::vector<std::string> 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<std::string> const& etcdv3::Member::get_peerURLs() const {
return peerURLs;
}
std::vector<std::string> const& etcdv3::Member::get_clientURLs() const {
return clientURLs;
}
bool etcdv3::Member::get_learner() const { return isLearner; }

View File

@ -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<int64_t> const& etcdv3::V3Response::get_leases() const {
return this->leases;
}
std::vector<etcdv3::Member> const& etcdv3::V3Response::get_members() const {
return this->members;
}

View File

@ -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";

136
tst/EtcdMemberTest.cpp Normal file
View File

@ -0,0 +1,136 @@
#include <cmath>
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <chrono>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>
#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<std::string>& 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<char*> c_args;
c_args.push_back(const_cast<char*>(etcd_path.c_str()));
for (const auto& arg : args) {
c_args.push_back(const_cast<char*>(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<std::string> 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());
}
}