From 6599cf706bee1f2fbe6ae5cdd34a6d6c6ab43bed Mon Sep 17 00:00:00 2001 From: Tao He Date: Sat, 15 Jul 2023 16:23:03 +0800 Subject: [PATCH] Fixes CI failures, document how to use in CMake Signed-off-by: Tao He --- README.md | 30 +++++++++++++ etcd-cpp-api-config.in.cmake | 5 ++- etcd/Client.hpp | 12 ++++++ etcd/SyncClient.hpp | 12 ++++++ etcd/v3/AsyncGRPC.hpp | 2 +- src/v3/AsyncGRPC.cpp | 81 ++++++++++++++++++++++++++---------- src/v3/Transaction.cpp | 18 +++++--- src/v3/action_constants.cpp | 2 +- tst/EtcdSyncTest.cpp | 20 ++++++--- tst/EtcdTest.cpp | 19 +++++---- 10 files changed, 157 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index fa90ce5..debc603 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,36 @@ dependencies have been successfully installed: cmake .. make -j$(nproc) && make install +## Using this package in your CMake project + +To use this package in your CMake project, you can either + +- install, then find the library using `find_package()`: + + ```cmake + find_package(etcd-cpp-apiv3 REQUIRED) + target_link_libraries(your_target PRIVATE etcd-cpp-api) + ``` + +- or, add this repository as a subdirectory in your project, and link the library directly: + + ```cmake + add_subdirectory(thirdparty/etcd-cpp-apiv3) + target_link_libraries(your_target PRIVATE etcd-cpp-api) + ``` + +- or, use [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html): + + ```cmake + include(FetchContent) + FetchContent_Declare( + etcd-cpp-apiv3 + https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3.git + ) + FetchContent_MakeAvailable(etcd-cpp-apiv3) + target_link_libraries(your_target PRIVATE etcd-cpp-api) + ``` + ## Compatible etcd version The _etcd-cpp-apiv3_ should work well with etcd > 3.0. Feel free to issue an issue to us on diff --git a/etcd-cpp-api-config.in.cmake b/etcd-cpp-api-config.in.cmake index befba7a..c1a9047 100644 --- a/etcd-cpp-api-config.in.cmake +++ b/etcd-cpp-api-config.in.cmake @@ -32,5 +32,8 @@ 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}, \netcd-cpp-apiv3 core libraries: ${ETCD_CPP_CORE_LIBRARIES}\ninclude directories: ${ETCD_CPP_INCLUDE_DIRS}" + "etcd-cpp-apiv3 version: @etcd-cpp-api_VERSION@\n" + "etcd-cpp-apiv3 libraries: ${ETCD_CPP_LIBRARIES}\n" + "etcd-cpp-apiv3 core libraries: ${ETCD_CPP_CORE_LIBRARIES}\n" + "include directories: ${ETCD_CPP_INCLUDE_DIRS}" ) diff --git a/etcd/Client.hpp b/etcd/Client.hpp index abaed4e..4f8a566 100644 --- a/etcd/Client.hpp +++ b/etcd/Client.hpp @@ -344,6 +344,8 @@ class Client { /** * Removes a single key. The key has to point to a plain, non directory entry. * @param key is the key to be deleted + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist. */ pplx::task rm(std::string const& key); @@ -351,6 +353,8 @@ class Client { * 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 + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist. */ pplx::task rm_if(std::string const& key, std::string const& old_value); @@ -361,6 +365,8 @@ class Client { * from the expected one. * @param key is the key to be deleted * @param old_index is the expected index of the existing value + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist. */ pplx::task rm_if(std::string const& key, int64_t old_index); @@ -370,6 +376,8 @@ class Client { * @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. + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted. */ pplx::task rmdir(std::string const& key, bool recursive = false); @@ -381,6 +389,8 @@ class Client { * * @param key is the directory to be created to be listed * @param range_end is the end of key range to be removed. + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted. */ pplx::task rmdir(std::string const& key, const char* range_end); @@ -389,6 +399,8 @@ class Client { * * @param key is the directory to be created to be listed * @param range_end is the end of key range to be removed. + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted. */ pplx::task rmdir(std::string const& key, std::string const& range_end); diff --git a/etcd/SyncClient.hpp b/etcd/SyncClient.hpp index ff387d5..6859096 100644 --- a/etcd/SyncClient.hpp +++ b/etcd/SyncClient.hpp @@ -397,6 +397,8 @@ class SyncClient { /** * Removes a single key. The key has to point to a plain, non directory entry. * @param key is the key to be deleted + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist. */ Response rm(std::string const& key); @@ -404,6 +406,8 @@ class SyncClient { * 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 + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist. */ Response rm_if(std::string const& key, std::string const& old_value); @@ -413,6 +417,8 @@ class SyncClient { * from the expected one. * @param key is the key to be deleted * @param old_index is the expected index of the existing value + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist. */ Response rm_if(std::string const& key, int64_t old_index); @@ -422,6 +428,8 @@ class SyncClient { * @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. + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted. */ Response rmdir(std::string const& key, bool recursive = false); @@ -433,6 +441,8 @@ class SyncClient { * * @param key is the directory to be created to be listed * @param range_end is the end of key range to be removed. + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted. */ Response rmdir(std::string const& key, const char* range_end); @@ -441,6 +451,8 @@ class SyncClient { * * @param key is the directory to be created to be listed * @param range_end is the end of key range to be removed. + * + * @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted. */ Response rmdir(std::string const& key, std::string const& range_end); diff --git a/etcd/v3/AsyncGRPC.hpp b/etcd/v3/AsyncGRPC.hpp index 7bdfcb9..c6a2d7c 100644 --- a/etcd/v3/AsyncGRPC.hpp +++ b/etcd/v3/AsyncGRPC.hpp @@ -76,7 +76,7 @@ class AsyncCampaignResponse : public etcdv3::V3Response { class AsyncDeleteResponse : public etcdv3::V3Response { public: AsyncDeleteResponse(){}; - void ParseResponse(bool prefix, DeleteRangeResponse& resp); + void ParseResponse(DeleteRangeResponse& resp); }; class AsyncHeadResponse : public etcdv3::V3Response { diff --git a/src/v3/AsyncGRPC.cpp b/src/v3/AsyncGRPC.cpp index 039384b..8cb8c7f 100644 --- a/src/v3/AsyncGRPC.cpp +++ b/src/v3/AsyncGRPC.cpp @@ -43,8 +43,7 @@ void etcdv3::AsyncCampaignResponse::ParseResponse(CampaignResponse& reply) { value.kvs.set_lease(leader.lease()); } -void etcdv3::AsyncDeleteResponse::ParseResponse(bool prefix, - DeleteRangeResponse& resp) { +void etcdv3::AsyncDeleteResponse::ParseResponse(DeleteRangeResponse& resp) { index = resp.header().revision(); if (resp.prev_kvs_size() == 0) { @@ -56,13 +55,15 @@ void etcdv3::AsyncDeleteResponse::ParseResponse(bool prefix, etcdv3::KeyValue kv; kv.kvs.CopyFrom(resp.prev_kvs(cnt)); values.push_back(kv); + prev_values.push_back(kv); } - if (!prefix) { - prev_value = values[0]; + // flatten values/prev_values 0 to value/prev_value + if (!values.empty()) { value = values[0]; - value.kvs.clear_value(); - values.clear(); + } + if (!prev_values.empty()) { + prev_value = prev_values[0]; } } } @@ -136,6 +137,7 @@ void etcdv3::AsyncPutResponse::ParseResponse(PutResponse& resp) { // get all previous values etcdv3::KeyValue kv; kv.kvs.CopyFrom(resp.prev_kv()); + prev_values.push_back(kv); prev_value = kv; } @@ -176,40 +178,55 @@ void etcdv3::AsyncTxnResponse::ParseResponse(TxnResponse& reply) { error_code = response.get_error_code(); } if (!response.get_error_message().empty()) { - error_message += "\n" + response.get_error_message(); + if (!error_message.empty()) { + error_message += "\n"; + } + error_message += response.get_error_message(); } for (auto const& value : response.get_values()) { values.emplace_back(value); } + for (auto const& prev_value : response.get_prev_values()) { + prev_values.emplace_back(prev_value); + } } else if (ResponseOp::ResponseCase::kResponsePut == resp.response_case()) { AsyncPutResponse response; response.ParseResponse(*(resp.mutable_response_put())); - prev_value.kvs.CopyFrom(response.get_prev_value().kvs); - if (error_code == 0) { error_code = response.get_error_code(); } if (!response.get_error_message().empty()) { - error_message += "\n" + response.get_error_message(); + if (!error_message.empty()) { + error_message += "\n"; + } + error_message += response.get_error_message(); } for (auto const& value : response.get_values()) { values.emplace_back(value); } + for (auto const& prev_value : response.get_prev_values()) { + prev_values.emplace_back(prev_value); + } } else if (ResponseOp::ResponseCase::kResponseDeleteRange == resp.response_case()) { AsyncDeleteResponse response; - response.ParseResponse(true, *(resp.mutable_response_delete_range())); - prev_value.kvs.CopyFrom(response.get_prev_value().kvs); + response.ParseResponse(*(resp.mutable_response_delete_range())); if (error_code == 0) { error_code = response.get_error_code(); } if (!response.get_error_message().empty()) { - error_message += "\n" + response.get_error_message(); + if (!error_message.empty()) { + error_message += "\n"; + } + error_message += response.get_error_message(); } for (auto const& value : response.get_values()) { values.emplace_back(value); } + for (auto const& prev_value : response.get_prev_values()) { + prev_values.emplace_back(prev_value); + } } else if (ResponseOp::ResponseCase::kResponseTxn == resp.response_case()) { AsyncTxnResponse response; response.ParseResponse(*(resp.mutable_response_txn())); @@ -218,7 +235,10 @@ void etcdv3::AsyncTxnResponse::ParseResponse(TxnResponse& reply) { error_code = response.get_error_code(); } if (!response.get_error_message().empty()) { - error_message += "\n" + response.get_error_message(); + if (!error_message.empty()) { + error_message += "\n"; + } + error_message += response.get_error_message(); } // skip @@ -341,11 +361,13 @@ etcdv3::AsyncCompareAndSwapAction::AsyncCompareAndSwapAction( etcdv3::Transaction txn; if (type == etcdv3::AtomicityType::PREV_VALUE) { txn.setup_compare_and_swap(parameters.key, parameters.old_value, - parameters.value); + parameters.value, parameters.lease_id); } else if (type == etcdv3::AtomicityType::PREV_INDEX) { txn.setup_compare_and_swap(parameters.key, parameters.old_revision, - parameters.value); + parameters.value, parameters.lease_id); } + // backwards compatibility + txn.add_success_range(parameters.key); response_reader = parameters.kv_stub->AsyncTxn(&context, *txn.txn_request, &cq_); @@ -377,7 +399,7 @@ etcdv3::AsyncDeleteAction::AsyncDeleteAction(ActionParameters&& params) DeleteRangeRequest del_request; detail::make_request_with_ranges(del_request, parameters.key, parameters.range_end, parameters.withPrefix); - del_request.set_prev_kv(true); + del_request.set_prev_kv(true /* fetch prev values */); response_reader = parameters.kv_stub->AsyncDeleteRange(&context, del_request, &cq_); @@ -392,8 +414,7 @@ etcdv3::AsyncDeleteResponse etcdv3::AsyncDeleteAction::ParseResponse() { del_resp.set_error_code(status.error_code()); del_resp.set_error_message(status.error_message()); } else { - del_resp.ParseResponse( - parameters.withPrefix || !parameters.range_end.empty(), reply); + del_resp.ParseResponse(reply); } return del_resp; } @@ -949,7 +970,9 @@ etcdv3::AsyncSetAction::AsyncSetAction(etcdv3::ActionParameters&& params, etcdv3::Transaction txn; isCreate = create; txn.add_compare_mod(parameters.key, 0 /* not exists */); - txn.add_success_put(parameters.key, parameters.key, parameters.lease_id); + txn.add_success_put(parameters.key, parameters.value, parameters.lease_id); + // backwards compatibility + txn.add_success_range(parameters.key); if (create) { txn.add_failure_put(parameters.key, parameters.value, parameters.lease_id); } else { @@ -1035,7 +1058,10 @@ etcdv3::AsyncUpdateAction::AsyncUpdateAction(etcdv3::ActionParameters&& params) : etcdv3::Action(std::move(params)) { etcdv3::Transaction txn; txn.add_compare_version(parameters.key, CompareResult::GREATER, 0); // exists - txn.add_success_put(parameters.key, parameters.value, parameters.lease_id); + txn.add_success_put(parameters.key, parameters.value, parameters.lease_id, + true /* for backwards compatibility */); + // backwards compatibility + txn.add_success_range(parameters.key); txn.add_failure_range(parameters.key); response_reader = parameters.kv_stub->AsyncTxn(&context, *txn.txn_request, &cq_); @@ -1065,8 +1091,17 @@ etcdv3::AsyncWatchAction::AsyncWatchAction(etcdv3::ActionParameters&& params) isCancelled.store(false); stream = parameters.watch_stub->AsyncWatch(&context, &cq_, (void*) etcdv3::WATCH_CREATE); - this->watch_id = - std::chrono::high_resolution_clock::now().time_since_epoch().count(); + // The unique watcher id causes the watcher cannot be cancelled as expected + // on Ubuntu 20.04. + // + // See CI failures: + // https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/actions/runs/5561397273/jobs/10159051536 + // + // Added in https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/pull/232 + // Removed in https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/pull/236 + // + // this->watch_id = + // std::chrono::high_resolution_clock::now().time_since_epoch().count(); // #ifndef NDEBUG // std::clog << "etcd-cpp-apiv3: watch_id: " << this->watch_id << std::endl; // #endif diff --git a/src/v3/Transaction.cpp b/src/v3/Transaction.cpp index 1841528..a7c0c37 100644 --- a/src/v3/Transaction.cpp +++ b/src/v3/Transaction.cpp @@ -268,7 +268,8 @@ void etcdv3::Transaction::setup_compare_and_delete( std::string const& delete_key, std::string const& range_end, const bool recursive) { this->add_compare_value(key, CompareResult::EQUAL, prev_value); - this->add_success_delete(delete_key, range_end, recursive); + this->add_success_delete(delete_key, range_end, recursive, + true /* for backwards compatibility */); this->add_failure_range(key); } @@ -278,7 +279,8 @@ void etcdv3::Transaction::setup_compare_or_delete(std::string const& key, std::string const& range_end, const bool recursive) { this->add_compare_value(key, CompareResult::NOT_EQUAL, prev_value); - this->add_success_delete(delete_key, range_end, recursive); + this->add_success_delete(delete_key, range_end, recursive, + true /* for backwards compatibility */); this->add_failure_range(key); } @@ -324,7 +326,8 @@ void etcdv3::Transaction::setup_compare_and_delete( std::string const& delete_key, std::string const& range_end, const bool recursive) { this->add_compare_mod(key, CompareResult::EQUAL, prev_revision); - this->add_success_delete(delete_key, range_end, recursive); + this->add_success_delete(delete_key, range_end, recursive, + true /* for backwards compatibility */); this->add_failure_range(key); } @@ -334,7 +337,8 @@ void etcdv3::Transaction::setup_compare_or_delete(std::string const& key, std::string const& range_end, const bool recursive) { this->add_compare_mod(key, CompareResult::NOT_EQUAL, prev_revision); - this->add_success_delete(delete_key, range_end, recursive); + this->add_success_delete(delete_key, range_end, recursive, + true /* for backwards compatibility */); this->add_failure_range(key); } @@ -344,11 +348,13 @@ void etcdv3::Transaction::setup_put(std::string const& key, } void etcdv3::Transaction::setup_delete(std::string const& key) { - this->add_success_delete(key); + this->add_success_delete(key, "", false, + true /* for backwards compatibility */); } void etcdv3::Transaction::setup_delete(std::string const& key, std::string const& range_end, const bool recursive) { - this->add_success_delete(key, range_end, recursive); + this->add_success_delete(key, range_end, recursive, + true /* for backwards compatibility */); } diff --git a/src/v3/action_constants.cpp b/src/v3/action_constants.cpp index dd82f50..8bac56c 100644 --- a/src/v3/action_constants.cpp +++ b/src/v3/action_constants.cpp @@ -5,7 +5,7 @@ char const* etcdv3::COMPARESWAP_ACTION = "compareAndSwap"; char const* etcdv3::UPDATE_ACTION = "update"; char const* etcdv3::SET_ACTION = "set"; char const* etcdv3::GET_ACTION = "get"; -char const* etcdv3::PUT_ACTION = "put"; +char const* etcdv3::PUT_ACTION = "set"; // alias char const* etcdv3::DELETE_ACTION = "delete"; char const* etcdv3::COMPAREDELETE_ACTION = "compareAndDelete"; char const* etcdv3::LOCK_ACTION = "lock"; diff --git a/tst/EtcdSyncTest.cpp b/tst/EtcdSyncTest.cpp index 1dee207..e588c28 100644 --- a/tst/EtcdSyncTest.cpp +++ b/tst/EtcdSyncTest.cpp @@ -12,6 +12,9 @@ TEST_CASE("sync operations") { etcd::SyncClient etcd(etcd_url); etcd.rmdir("/test", true); + etcd::Response res; + int64_t index; + // add CHECK(0 == etcd.add("/test/key1", "42").error_code()); CHECK(etcd::ERROR_KEY_ALREADY_EXISTS == @@ -22,7 +25,9 @@ TEST_CASE("sync operations") { CHECK(0 == etcd.modify("/test/key1", "43").error_code()); CHECK(etcd::ERROR_KEY_NOT_FOUND == etcd.modify("/test/key2", "43").error_code()); // Key not found - CHECK("43" == etcd.modify("/test/key1", "42").prev_value().as_string()); + res = etcd.modify("/test/key1", "42"); + CHECK(0 == res.error_code()); + CHECK("43" == res.prev_value().as_string()); // set CHECK(0 == etcd.set("/test/key1", "43").error_code()); // overwrite @@ -60,7 +65,7 @@ TEST_CASE("sync operations") { // compare and swap etcd.set("/test/key1", "42"); - int64_t index = etcd.modify_if("/test/key1", "43", "42").index(); + index = etcd.modify_if("/test/key1", "43", "42").index(); CHECK(etcd::ERROR_COMPARE_FAILED == etcd.modify_if("/test/key1", "44", "42").error_code()); REQUIRE(etcd.modify_if("/test/key1", "44", index).is_ok()); @@ -71,16 +76,20 @@ TEST_CASE("sync operations") { etcd.set("/test/key1", "42"); CHECK(etcd::ERROR_COMPARE_FAILED == etcd.rm_if("/test/key1", "43").error_code()); - CHECK(0 == etcd.rm_if("/test/key1", "42").error_code()); + res = etcd.rm_if("/test/key1", "42"); + CHECK( + (0 == res.error_code() || etcd::ERROR_KEY_NOT_FOUND == res.error_code())); // atomic compare-and-delete based on prevIndex index = etcd.set("/test/key1", "42").index(); CHECK(etcd::ERROR_COMPARE_FAILED == etcd.rm_if("/test/key1", index - 1).error_code()); - CHECK(0 == etcd.rm_if("/test/key1", index).error_code()); + res = etcd.rm_if("/test/key1", index); + CHECK( + (0 == res.error_code() || etcd::ERROR_KEY_NOT_FOUND == res.error_code())); // leasegrant - etcd::Response res = etcd.leasegrant(60); + res = etcd.leasegrant(60); REQUIRE(res.is_ok()); CHECK(60 == res.value().ttl()); CHECK(0 < res.value().lease()); @@ -96,6 +105,7 @@ TEST_CASE("sync operations") { res = etcd.set("/test/key1", "43", leaseid); REQUIRE(0 == res.error_code()); CHECK("set" == res.action()); + res = etcd.get("/test/key1"); CHECK(leaseid == res.value().lease()); // modify with lease diff --git a/tst/EtcdTest.cpp b/tst/EtcdTest.cpp index 7739b76..74d6a5a 100644 --- a/tst/EtcdTest.cpp +++ b/tst/EtcdTest.cpp @@ -91,12 +91,13 @@ TEST_CASE("set a key") { CHECK(0 == etcd.set("/test", "42").get().error_code()); // Not a file // set with ttl - resp = etcd.set("/test/key1", "50", 10).get(); + resp = etcd.set("/test/key1", "50").get(); REQUIRE(0 == resp.error_code()); // overwrite CHECK("set" == resp.action()); CHECK("43" == resp.prev_value().as_string()); + resp = etcd.get("/test/key1").get(); CHECK("50" == resp.value().as_string()); - CHECK(0 < resp.value().lease()); + CHECK(0 == resp.value().lease()); } TEST_CASE("atomic compare-and-swap") { @@ -115,11 +116,11 @@ TEST_CASE("atomic compare-and-swap") { CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code()); CHECK("etcd-cpp-apiv3: compare failed" == res.error_message()); - // modify fails the second time + // modify fails on non-existing keys res = etcd.modify_if("/test/key222", "44", "42").get(); CHECK(!res.is_ok()); - CHECK(etcd::ERROR_KEY_NOT_FOUND == res.error_code()); - CHECK("etcd-cpp-apiv3: key not found" == res.error_message()); + CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code()); + CHECK("etcd-cpp-apiv3: compare failed" == res.error_message()); } TEST_CASE("delete a value") { @@ -144,10 +145,10 @@ TEST_CASE("delete a value") { CHECK(modify_index == resp.prev_value().modified_index()); CHECK(version == resp.prev_value().version()); CHECK("delete" == resp.action()); - CHECK(modify_index == resp.value().modified_index()); CHECK(create_index == resp.value().created_index()); + CHECK(modify_index == resp.value().modified_index()); CHECK(version == resp.value().version()); - CHECK("" == resp.value().as_string()); + CHECK("43" == resp.value().as_string()); CHECK("/test/key1" == resp.value().key()); } @@ -558,6 +559,8 @@ TEST_CASE("lease grant") { res = etcd.set("/test/key1", "43", leaseid).get(); REQUIRE(0 == res.error_code()); // overwrite CHECK("set" == res.action()); + res = etcd.get("/test/key1").get(); + REQUIRE(0 == res.error_code()); // overwrite CHECK(leaseid == res.value().lease()); // change with lease id @@ -566,6 +569,8 @@ TEST_CASE("lease grant") { res = etcd.set("/test/key1", "43", leaseid).get(); REQUIRE(0 == res.error_code()); // overwrite CHECK("set" == res.action()); + res = etcd.get("/test/key1").get(); + REQUIRE(0 == res.error_code()); // overwrite CHECK(leaseid == res.value().lease()); // failure to attach lease id