Giter VIP home page Giter VIP logo

etcd-cpp-apiv3's Introduction

etcd-cpp-apiv3

The etcd-cpp-apiv3 is a C++ API for etcd's v3 client API, i.e., ETCDCTL_API=3.

Build and Test

Supported OS environments

  • Linux

    • Ubuntu 18.04, requires upgrade gRPC libraries (tested with 1.27.x).
    • Ubuntu 20.04
    • CentOS 8 (tested with 1.27.x)
  • MacOS

    • MacOS 10.15
    • MacOS 11.0
  • Windows

Supported etcd versions:

Requirements

  1. boost and openssl (Note that boost is only required if you need the asynchronous runtime)

    • On Ubuntu, above requirement could be installed as:

      apt-get install libboost-all-dev libssl-dev
      
    • On MacOS, above requirement could be installed as:

      brew install boost openssl
      
  2. protobuf & gRPC

    • On Ubuntu, above requirements related to protobuf and gRPC can be installed as:

      apt-get install libgrpc-dev \
              libgrpc++-dev \
              libprotobuf-dev \
              protobuf-compiler-grpc
      
    • On MacOS, above requirements related to protobuf and gRPC can be installed as:

      brew install grpc protobuf
      
    • When building grpc from source code (e.g., on Ubuntu 18.04 and on CentOS), if the system-installed openssl is preferred, you need to add -DgRPC_SSL_PROVIDER=package when building gRPC with CMake.

  3. cpprestsdk, the latest version of master branch on github should work, you can build and install this dependency using cmake with:

     git clone https://github.com/microsoft/cpprestsdk.git
     cd cpprestsdk
     mkdir build && cd build
     cmake .. -DCPPREST_EXCLUDE_WEBSOCKETS=ON
     make -j$(nproc) && make install
    

API documentation

The etcd-cpp-apiv3 doesn't maintain a website for documentation, for detail usage of the etcd APIs, please refer to the "etcd operations" section in README, and see the detail C++ interfaces in Client.hpp and SyncClient.hpp.

Build and install

The etcd-cpp-apiv3 library could be easily built and installed using cmake, after all above dependencies have been successfully installed:

git clone https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3.git
cd etcd-cpp-apiv3
mkdir build && cd build
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():

    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:

    add_subdirectory(thirdparty/etcd-cpp-apiv3)
    target_link_libraries(your_target PRIVATE etcd-cpp-api)
  • or, use FetchContent:

    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 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 and issue#207 for more discussion about the implementation of underlying thread model).

The etcd-cpp-apiv3 library supports both synchronous and asynchronous runtime, controlled by the cmake option BUILD_ETCD_CORE_ONLY=ON/OFF (defaults to OFF).

  • When it is set as OFF: the library artifact name will be libetcd-cpp-api.{a,so,dylib,lib,dll} and a cmake target etcd-cpp-api is exported and pointed to it. The library provides both synchronous runtime (etcd/SyncClient.hpp) and asynchronous runtime (etcd/Client.hpp), and the cpprestsdk is a required dependency.
  • When it is set as ON: the library artifact name will be libetcd-cpp-api-core.{a,so,dylib,lib,dll} and a cmake target etcd-cpp-api is exported and pointed to it. The library provides only the synchronous runtime (etcd/SyncClient.hpp), and the cpprestsdk won't be required.

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. Note that the asynchronous runtime requires cpprestsdk and will setup a thread pool in the background.

Warning: users cannot link both libetcd-cpp-api.{a,so,dylib,lib,dll} and libetcd-cpp-api-core.{a,so,dylib,lib,dll} to same program.

Usage

  etcd::Client etcd("http://127.0.0.1:2379");
  etcd::Response response = etcd.get("/test/key1").get();
  std::cout << response.value().as_string();

Methods of the etcd client object are sending the corresponding gRPC requests and are returning immediately with a pplx::task object. The task object is responsible for handling the reception of the HTTP response as well as parsing the gRPC of the response. All of this is done asynchronously in a background thread so you can continue your code to do other operations while the current etcd operation is executing in the background or you can wait for the response with the wait() or get() methods if a synchronous behavior is enough for your needs. These methods are blocking until the HTTP response arrives or some error situation happens. get() method also returns the etcd::Response object.

  etcd::Client etcd("http://127.0.0.1:2379");
  pplx::task<etcd::Response> response_task = etcd.get("/test/key1");
  // ... do something else
  etcd::Response response = response_task.get();
  std::cout << response.value().as_string();

The pplx library allows to do even more. You can attach continuation objects to the task if you do not care about when the response is coming you only want to specify what to do then. This can be achieved by calling the then method of the task, giving a function object parameter to it that can be used as a callback when the response is arrived and processed. The parameter of this callback should be either a etcd::Response or a pplx::task<etcd:Response>. You should probably use a C++ lambda function here as a callback.

  etcd::Client etcd("http://127.0.0.1:2379");
  etcd.get("/test/key1").then([](etcd::Response response)
  {
    std::cout << response.value().as_string();
  });

  // ... your code can continue here without any delay

Your lambda function should have a parameter of type etcd::Response or pplx::task<etcd::Response>. In the latter case you can get the actual etcd::Response object with the get() function of the task. Calling get can raise exceptions so this is the way how you can catch the errors generated by the REST interface. The get() call will not block in this case since the response has been already arrived (we are inside the callback).

  etcd::Client etcd("http://127.0.0.1:2379");
  etcd.get("/test/key1").then([](pplx::task<etcd::Response> response_task)
  {
    try
    {
      etcd::Response response = response_task.get(); // can throw
      std::cout << response.value().as_string();
    }
    catch (std::exception const & ex)
    {
      std::cerr << ex.what();
    }
  });

  // ... your code can continue here without any delay

Multiple endpoints

Connecting to multiple endpoints is supported:

  // multiple endpoints are separated by comma
  etcd::Client etcd("http://a.com:2379,http://b.com:2379,http://c.com:2379");

  // or, separated semicolon
  etcd::Client etcd("http://a.com:2379;http://b.com:2379;http://c.com:2379");

IPv6

Connecting to IPv6 endpoints is supported:

  etcd::Client etcd("http://::1:2379");

Behind the screen, gRPC's load balancer is used and the round-robin strategy will be used by default.

Etcd authentication

v3 authentication

Etcd v3 authentication has been supported. The Client::Client could accept a username and password as arguments and handle the authentication properly.

  etcd::Client etcd("http://127.0.0.1:2379", "root", "root");

Or the etcd client can be constructed explicitly:

  etcd::Client *etcd = etcd::Client::WithUser(
                    "http://127.0.0.1:2379", "root", "root");

The default authentication token will be expired every 5 minutes (300 seconds), which is controlled by the --auth-token-ttl flag of etcd. When constructing a etcd client, a customized TTL value is allow:

  etcd::Client etcd("http://127.0.0.1:2379", "root", "root", 300);

Enabling v3 authentication requires a bit more work for older versions etcd (etcd 3.2.x and etcd 3.3.x). First you need to set the ETCDCTL_API=3, then

  • add a user, and type the password:
printf 'root\nroot\n' | /usr/local/bin/etcdctl user add root
  • enabling authentication:
/usr/local/bin/etcdctl auth enable
  • disable authentication:
/usr/local/bin/etcdctl --user="root:root" auth disable

transport security

Etcd transport security and certificate based authentication have been supported as well. The Client::Client could accept arguments ca , cert and privkey for CA cert, cert and private key files for the SSL/TLS transport and authentication. Note that the later arguments cert and privkey could be empty strings or omitted if you just need secure transport and don't enable certificate-based client authentication (using the --client-cert-auth arguments when launching etcd server).

  etcd::Client etcd("https://127.0.0.1:2379",
                    "example.rootca.cert", "example.cert", "example.key",
                    "round_robin");

Or the etcd client can be constructed explicitly:

  etcd::Client *etcd = etcd::Client::WithSSL(
                    "https://127.0.0.1:2379",
                    "example.rootca.cert", "example.cert", "example.key");

Using secure transport but not certificated-based client authentication:

  etcd::Client *etcd = etcd::Client::WithSSL(
                    "https://127.0.0.1:2379", "example.rootca.cert");

For more details about setup about security communication between etcd server and client, please refer to transport security in etcd documentation and an example about setup etcd with transport security using openssl.

We also provide a tool setup-ca.sh as a helper for development and testing.

transport security & multiple endpoints

If you want to use multiple https:// endpoints, and you are working with self-signed certificates, you may encountered errors like

error: 14: connections to all backends failing

That means your DNS have some problems with your DNS resolver and SSL authority, you could put a domain name (a host name) to the SANS field when self-signing your certificate, e.g,

"sans": [
  "etcd",
  "127.0.0.1",
  "127.0.0.2",
  "127.0.0.3"
],

And pass a target_name_override arguments to WithSSL,

  etcd::Client *etcd = etcd::Client::WithSSL(
                    "https://127.0.0.1:2379,https://127.0.0.2:2479",
                    "example.rootca.cert", "example.cert", "example.key", "etcd");

For more discussion about this feature, see also #87, grpc#20186 and grpc#22119.

Fine-grained gRPC channel arguments

By default the etcd-cpp-apiv3 library will set the following arguments for transport layer

  • GRPC_ARG_MAX_SEND_MESSAGE_LENGTH to INT_MAX
  • GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH to INT_MAX

If load balancer strategy is specified, the following argument will be set

  • GRPC_ARG_LB_POLICY_NAME

When transport security is enabled and target_name_override is specified when working with SSL, the following argument will be set

  • GRPC_SSL_TARGET_NAME_OVERRIDE_ARG

Further, all variants of constructors for etcd::Client accepts an extra grpc::ChannelArguments argument which can be used for fine-grained control the gRPC settings, e.g.,

  grpc::ChannelArguments grpc_args;
  grpc_args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 2000);
  grpc_args.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 6000);
  grpc_args.SetInt(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);

  etcd::Client etcd("http://127.0.0.1:2379", grpc_args);

For more motivation and discussion about the above design, please refer to issue-103.

gRPC timeout when waiting for responses

gRPC Timeout is long-standing missing pieces in the etcd-cpp-apiv3 library. The timeout has been supported via a set_grpc_timeout interfaces on the client,

  template <typename Rep = std::micro>
  void set_grpc_timeout(std::chrono::duration<Rep> const &timeout)

Any std::chrono::duration value can be used to set the grpc timeout, e.g.,

  etcd.set_grpc_timeout(std::chrono::seconds(5));

Note that the timeout value is the "timeout" when waiting for responses upon the gRPC channel, i.e., CompletionQueue::AsyncNext. It doesn't means the timeout between issuing a .set() method getting the etcd::Response, as in the async mode the such a time duration is unpredictable and the gRPC timeout should be enough to avoid deadly waiting (e.g., waiting for a lock()).

Error code in responses

The class etcd::Response may yield an error code and error message when error occurs,

  int error_code() const;
  std::string const & error_message() const;

  bool is_ok() const;

The error code would be 0 when succeed, otherwise the error code might be

  extern const int ERROR_GRPC_OK;
  extern const int ERROR_GRPC_CANCELLED;
  extern const int ERROR_GRPC_UNKNOWN;
  extern const int ERROR_GRPC_INVALID_ARGUMENT;
  extern const int ERROR_GRPC_DEADLINE_EXCEEDED;
  extern const int ERROR_GRPC_NOT_FOUND;
  extern const int ERROR_GRPC_ALREADY_EXISTS;
  extern const int ERROR_GRPC_PERMISSION_DENIED;
  extern const int ERROR_GRPC_UNAUTHENTICATED;
  extern const int ERROR_GRPC_RESOURCE_EXHAUSTED;
  extern const int ERROR_GRPC_FAILED_PRECONDITION;
  extern const int ERROR_GRPC_ABORTED;
  extern const int ERROR_GRPC_OUT_OF_RANGE;
  extern const int ERROR_GRPC_UNIMPLEMENTED;
  extern const int ERROR_GRPC_INTERNAL;
  extern const int ERROR_GRPC_UNAVAILABLE;
  extern const int ERROR_GRPC_DATA_LOSS;

  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;

Etcd operations

Reading a value

You can read a value with the get() method of the client instance. The only parameter is the key to be read. If the read operation is successful then the value of the key can be acquired with the value() method of the response. Success of the operation can be checked with the is_ok() method of the response. In case of an error, the error_code() and error_message() methods can be called for some further detail.

Please note that there can be two kind of error situations. There can be some problem with the communication between the client and the etcd server. In this case the `get()``` method of the response task will throw an exception as shown above. If the communication is ok but there is some problem with the content of the actual operation, like attempting to read a non-existing key then the response object will give you all the details. Let's see this in an example.

The Value object of the response also holds some extra information besides the string value of the key. You can also get the index number of the creation and the last modification of this key with the created_index() and the modified_index() methods.

  etcd::Client etcd("http://127.0.0.1:2379");
  pplx::task<etcd::Response> response_task = etcd.get("/test/key1");

  try
  {
    etcd::Response response = response_task.get(); // can throw
    if (response.is_ok())
      std::cout << "successful read, value=" << response.value().as_string();
    else
      std::cout << "operation failed, details: " << response.error_message();
  }
  catch (std::exception const & ex)
  {
    std::cerr << "communication problem, details: " << ex.what();
  }

Put a value

You can put a key-value pair to etcd with the the put() method of the client instance. The only parameter is the key and value to be put

  etcd::Client etcd("http://127.0.0.1:2379");
  pplx::task<etcd::Response> response_task = etcd.put("foo", "bar");

Modifying a value

Setting the value of a key can be done with the set() method of the client. You simply pass the key and the value as string parameters and you are done. The newly set value object can be asked from the response object exactly the same way as in case of the reading (with the value() method). This way you can check for example the index value of your modification. You can also check what was the previous value that this operation was overwritten. You can do that with the prev_value() method of the response object.

  etcd::Client etcd("http://127.0.0.1:2379");
  pplx::task<etcd::Response> response_task = etcd.set("/test/key1", "42");

  try
  {
    etcd::Response response = response_task.get();
    if (response.is_ok())
      std::cout << "The new value is successfully set, previous value was "
                << response.prev_value().as_string();
    else
      std::cout << "operation failed, details: " << response.error_message();
  }
  catch (std::exception const & ex)
  {
    std::cerr << "communication problem, details: " << ex.what();
  }

The set method creates a new leaf node if it weren't exists already or modifies an existing one. There are a couple of other modification methods that are executing the write operation only upon some specific conditions.

  • add(key, value) creates a new value if it's key does not exists and returns a "Key already exists" error otherwise (error code ERROR_KEY_ALREADY_EXISTS)
  • modify(key, value) modifies an already existing value or returns a "etcd-cpp-apiv3: key not found" error otherwise (error code KEY_NOT_FOUND)
  • modify_if(key, value, old_value) modifies 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)
  • modify_if(key, value, old_index) modifies 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)

Deleting a value

Values can be deleted with the rm method passing the key to be deleted as a parameter. The key 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 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 the previous value equals with old_index. If the indices does not match returns with "Compare failed" error (code ERROR_COMPARE_FAILED)

Handling directory nodes

Directory nodes are not supported anymore in etcdv3. However, ls and rmdir will list/delete keys defined by the prefix. mkdir method is removed since etcdv3 treats everything as keys.

  1. Creating a directory:

    Creating a directory is not supported anymore in etcdv3 cpp client. Users should remove the API from their code.

  2. Listing a directory:

    Listing directory in etcd3 cpp client will return all keys that matched the given prefix recursively.

      etcd.set("/test/key1", "value1").wait();
      etcd.set("/test/key2", "value2").wait();
      etcd.set("/test/key3", "value3").wait();
      etcd.set("/test/subdir/foo", "foo").wait();
    
      etcd::Response resp = etcd.ls("/test").get();

    resp.key() will have the following values:

    /test/key1s
    /test/key2
    /test/key3
    /test/subdir/foo
    

    Note: Regarding the returned keys when listing a directory:

    • In etcdv3 cpp client, resp.key(0) will return "/test/new_dir/key1" since everything is treated as keys in etcdv3.
    • While in etcdv2 cpp client it will return "key1" and "/test/new_dir" directory should be created first before you can set "key1".

    When you list a directory the response object's keys() and values() methods gives you a vector of key names and values. The value() method with an integer parameter also returns with the i-th element of the values vector, so response.values()[i] == response.value(i).

      etcd::Client etcd("http://127.0.0.1:2379");
      etcd::Response resp = etcd.ls("/test/new_dir").get();
      for (int i = 0; i < resp.keys().size(); ++i)
      {
        std::cout << resp.keys(i);
        std::cout << " = " << resp.value(i).as_string() << std::endl;
      }

    etcd-cpp-apiv3 supports lists keys only without fetching values from etcd server:

      etcd::Client etcd("http://127.0.0.1:2379");
      etcd::Response resp = etcd.keys("/test/new_dir").get();
      for (int i = 0; i < resp.keys().size(); ++i)
      {
        std::cout << resp.keys(i);
      }
  3. Removing directory:

    If you want the delete recursively then you have to pass a second true parameter to rmdir and supply a key. This key will be treated as a prefix. All keys that match the prefix will be deleted. All deleted keys will be placed in response.values() and response.keys(). This parameter defaults to false.

      etcd::Client etcd("http://127.0.0.1:2379");
      etcd.set("/test/key1", "foo");
      etcd.set("/test/key2", "bar");
      etcd.set("/test/key3", "foo_bar");
      etcd::Response resp = etcd.rmdir("/test", true).get();
      for (int i = 0; i < resp.keys().size(); ++i)
      {
        std::cout << resp.keys(i);
        std::cout << " = " << resp.value(i).as_string() << std::endl;
      }

    However, if recursive parameter is false, functionality will be the same as just deleting a key. The key supplied will NOT be treated as a prefix and will be treated as a normal key name.

Using binary data as key and value

Etcd itself support using arbitrary binary data as the key and value, i.e., the key and value can contain \NUL (\0) and not necessary NUL-terminated strings. std::string in C++ supports embed \0 as well, but please note that when constructing std::string from a C-style string the string will be terminated by the first \0 character. Rather, you need to use the constructor with the count parameter explicitly. When unpack a std::string that contains \0, you need .data(),

  std::string key = "key-foo\0bar";
  std::string value = "value-foo\0bar";
  etcd.put(key, value).wait();

Lock

Etcd lock has been supported as follows:

  etcd::Client etcd("http://127.0.0.1:2379");
  etcd.lock("/test/lock");

It will create a lease and a keep-alive job behind the screen, the lease will be revoked until the lock is unlocked.

Users can also feed their own lease directory for lock:

  etcd::Client etcd("http://127.0.0.1:2379");
  etcd.lock_with_lease("/test/lock", lease_id);

Note that the arguments for unlock() is the the same key that used for lock(), but the response.lock_key() that return by lock():

  etcd::Client etcd("http://127.0.0.1:2379");

  // lock
  auto response = etcd.lock("/test/lock").get();

  // unlock
  auto _ = etcd.unlock(response.lock_key()).get();

Watching for changes

Watching for a change is possible with the watch() operation of the client. The watch method simply does not deliver a response object until the watched value changes in any way (modified or deleted). When a change happens the returned result object will be the same as the result object of the modification operation. So if the change is triggered by a value change, then response.action() will return "set", response.value() will hold the new value and response.prev_value() will contain the previous value. In case of a delete response.action() will return "delete", response.value() will be empty and should not be called at all and response.prev_value() will contain the deleted value.

As mentioned in the section "handling directory nodes", directory nodes are not supported anymore in etcdv3.

However it is still possible to watch a whole "directory subtree", or more specifically a set of keys that match the prefix, for changes with passing true to the second recursive parameter of watch (this parameter defaults to false if omitted). In this case the modified value object's key() method can be handy to determine what key is actually changed. Since this can be a long lasting operation you have to be prepared that is terminated by an exception and you have to restart the watch operation.

The watch also accepts an index parameter that specifies what is the first change we are interested about. Since etcd stores the last couple of modifications with this feature you can ensure that your client does not miss a single change.

Here is an example how you can watch continuously for changes of one specific key.

void watch_for_changes()
{
  etcd.watch("/nodes", index + 1, true).then([this](pplx::task<etcd::Response> resp_task)
  {
    try
    {
      etcd::Response resp = resp_task.get();
      index = resp.index();
      std::cout << resp.action() << " " << resp.value().as_string() << std::endl;
    }
    catch(...) {}
    watch_for_changes();
  });
}

At first glance it seems that watch_for_changes() calls itself on every value change but in fact it just sends the asynchronous request, sets up a callback for the response and then returns. The callback is executed by some thread from the pplx library's thread pool and the callback (in this case a small lambda function actually) will call watch_for_changes() again from there.

Watcher Class

Users can watch a key indefinitely or until user cancels the watch. This can be done by instantiating a Watcher class. The supplied callback function in Watcher class will be called every time there is an event for the specified key. Watch stream will be cancelled either by user implicitly calling Cancel() or when watcher class is destroyed.

  etcd::Watcher watcher("http://127.0.0.1:2379", "/test", printResponse);
  etcd.set("/test/key", "42"); /* print response will be called */
  etcd.set("/test/key", "43"); /* print response will be called */
  watcher.Cancel();
  etcd.set("/test/key", "43"); /* print response will NOT be called,
                                  since watch is already cancelled */

Watcher re-connection

A watcher will be disconnected from etcd server in some cases, for some examples, the etcd server is restarted, or the network is temporarily unavailable. It is users' responsibility to decide if a watcher should re-connect to the etcd server.

Here is an example how users can make a watcher re-connect to server after disconnected.

// wait the client ready
void wait_for_connection(etcd::Client &client) {
  // wait until the client connects to etcd server
  while (!client.head().get().is_ok()) {
    sleep(1);
  }
}

// a loop for initialized a watcher with auto-restart capability
void initialize_watcher(const std::string& endpoints,
                        const std::string& prefix,
                        std::function<void(etcd::Response)> callback,
                        std::shared_ptr<etcd::Watcher>& watcher) {
  etcd::Client client(endpoints);
  wait_for_connection(client);

  // Check if the failed one has been cancelled first
  if (watcher && watcher->Cancelled()) {
    std::cout << "watcher's reconnect loop been cancelled" << std::endl;
    return;
  }
  watcher.reset(new etcd::Watcher(client, prefix, callback, true));

  // Note that lambda requires `mutable`qualifier.
  watcher->Wait([endpoints, prefix, callback,
    /* By reference for renewing */ &watcher](bool cancelled) mutable {
    if (cancelled) {
      std::cout << "watcher's reconnect loop stopped as been cancelled" << std::endl;
      return;
    }
    initialize_watcher(endpoints, prefix, callback, watcher);
  });
}

The functionalities can be used as

std::string endpoints = "http://127.0.0.1:2379";
std::function<void(Response)> callback = printResponse;
const std::string prefix = "/test/key";

// the watcher initialized in this way will auto re-connect to etcd
std::shared_ptr<etcd::Watcher> watcher;
initialize_watcher(endpoints, prefix, callback, watcher);

For a complete runnable example, see also ./tst/RewatchTest.cpp. Note that you shouldn't use the watcher itself inside the Wait() callback as the callback will be invoked in a separate detached thread where the watcher may have been destroyed.

Requesting for lease

Users can request for lease which is governed by a time-to-live(TTL) value given by the user. Moreover, user can attached the lease to a key(s) by indicating the lease id in add(), set(), modify() and modify_if(). Also the ttl will that was granted by etcd server will be indicated in ttl().

  etcd::Client etcd("http://127.0.0.1:2379");
  etcd::Response resp = etcd.leasegrant(60).get();
  etcd.set("/test/key2", "bar", resp.value().lease());
  std::cout << "ttl" << resp.value().ttl();

The lease can be revoked by

  etcd.leaserevoke(resp.value().lease());

A lease can also be attached with a KeepAlive object at the creation time,

  std::shared_ptr<etcd::KeepAlive> keepalive = etcd.leasekeepalive(60).get();
  std::cout << "lease id: " << keepalive->Lease();

The remaining time-to-live of a lease can be inspected by

  etcd::Response resp2 = etcd.leasetimetolive(resp.value().lease()).get();
  std::cout << "ttl" << resp.value().ttl();

Keep alive

Keep alive for leases is implemented using a separate class KeepAlive, which can be used as:

  etcd::KeepAlive keepalive(etcd, ttl, lease_id);

It will perform a period keep-alive action before it is cancelled explicitly, or destructed implicitly.

KeepAlive may fails (e.g., when the etcd server stopped unexpectedly), the constructor of KeepAlive could accept a handler of type std::function<std::exception_ptr> and the handler will be invoked when exception occurs during keeping it alive.

Note that the handler will invoked in a separated thread, not the thread where the KeepAlive object is constructed.

  std::function<void (std::exception_ptr)> handler = [](std::exception_ptr eptr) {
    try {
        if (eptr) {
            std::rethrow_exception(eptr);
        }
    } catch(const std::runtime_error& e) {
        std::cerr << "Connection failure \"" << e.what() << "\"\n";
    } catch(const std::out_of_range& e) {
        std::cerr << "Lease expiry \"" << e.what() << "\"\n";
    }
  };
  etcd::KeepAlive keepalive(etcd, handler, ttl, lease_id);

Without handler, the internal state can be checked via KeepAlive::Check() and it will rethrow the async exception when there are errors during keeping the lease alive.

Note that even with handler, the KeepAlive::Check() still rethrow if there's an async exception. When the library is built with -fno-exceptions, the handler argument and the Check() method will abort the program when there are errors during keeping the lease alive.

Etcd transactions

Etcd v3's Transaction APIs is supported via the etcdv3::Transaction interfaces. A set of convenient APIs are use to add operations to a transaction, e.g.,

  etcdv3::Transaction txn;
  txn.setup_put("/test/x1", "1");
  txn.setup_put("/test/x2", "2");
  txn.setup_put("/test/x3", "3");
  etcd::Response resp = etcd.txn(txn).get();

Transactions in etcd supports set a set of comparison targets to specify the condition of transaction, e.g.,

  etcdv3::Transaction txn;

  // setup the conditions
  txn.add_compare_value("/test/x1", "1");
  txn.add_compare_value("/test/x2", "2");

  // or, compare the last modified revision
  txn.add_compare_mod("/test/x3", 0);  // not exists
  txn.add_compare_mod("/test/x4", etcdv3::CompareResult::GREATER, 1234);  // the modified revision is greater than 1234

High-level APIs (e.g., compare_and_create, compare_and_swap) are also provided, e.g., fetch-and-add operation can be implemented as

  auto fetch_and_add = [](etcd::Client& client,
                          std::string const& key) -> void {
    auto value = stoi(client.get(key).get().value().as_string());
    while (true) {
      auto txn = etcdv3::Transaction();
      txn.setup_compare_and_swap(key, std::to_string(value),
                                 std::to_string(value + 1));
      etcd::Response resp = client.txn(txn).get();
      if (resp.is_ok()) {
        break;
      }
      value = stoi(resp.value().as_string());
    }
  };

See full example of the usages of transaction APIs, please refer to ./tst/TransactionTest.cpp, for full list of the transaction operation APIs, see ./etcd/v3/Transaction.hpp.

Election API

Etcd v3's election APIs are supported via the following interfaces,

pplx::task<Response> campaign(std::string const &name, int64_t lease_id,
                              std::string const &value);

pplx::task<Response> proclaim(std::string const &name, int64_t lease_id,
                              std::string const &key, int64_t revision,
                              std::string const &value);

pplx::task<Response> leader(std::string const &name);

std::unique_ptr<SyncClient::Observer> observe(std::string const &name);

pplx::task<Response> resign(std::string const &name, int64_t lease_id,
                            std::string const &key, int64_t revision);

Note that if grpc timeout is set, campaign() will return an timeout error response if it cannot acquire the election ownership within the timeout period. Otherwise will block until become the leader.

The Observer returned by observe() can be use to monitor the changes of election ownership. The observer stream will be canceled when been destructed.

  std::unique_ptr<etcd::SyncClient::Observer> observer = etcd.observe("test");

  // wait one change event, blocked execution
  etcd::Response resp = observer->WaitOnce();

  // wait many change events, blocked execution
  for (size_t i = 0; i < ...; ++i) {
    etcd::Response resp = observer->WaitOnce();
    ...
  }

  // cancel the observer
  observer.reset(nullptr);

for more details, please refer to etcd/Client.hpp and tst/ElectionTest.cpp.

-fno-exceptions

The etcd-cpp-apiv3 library supports to be built with -fno-exceptions flag, controlled by the cmake option BUILD_WITH_NO_EXCEPTIONS=ON/OFF (defaults to OFF).

When building with -fno-exceptions, the library will abort the program under certain circumstances, e.g., when calling .Check() method of KeepAlive and there are errors during keeping the lease alive,

TODO

  1. Cancellation of asynchronous calls(except for watch)

License

This project is licensed under the BSD-3-Clause license - see the LICENSE.

etcd-cpp-apiv3's People

Contributors

ashish-billore avatar cayoest avatar chenrui333 avatar clementperon avatar csatarigergely avatar daniel0076 avatar diskein avatar fbdlampayan avatar fmatthew5876 avatar huangled avatar hz-bin avatar jonliu1993 avatar mszy avatar niyue avatar penfree avatar pyssling avatar rockeet avatar sighingnow avatar siyuan0322 avatar zhanghan-mq avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

etcd-cpp-apiv3's Issues

Build and Install Instructions are not clear

Hi,

Is this library usable or just experimental ? The instructions on how to build and install don't even exist. The commands listed for compiling proto files also don't work as is. Given, the partial information, here is what I had to do after installing all the dependencies listed on the page:

  1. download the source code (or git clone)
  2. cd // the directory where code is cloned
  3. cd proto/
  4. protoc -I . --grpc_out=. --plugin=protoc-gen-grpc=which grpc_cpp_plugin ./rpc.proto
  5. protoc -I . --cpp_out=. ./*.proto
  6. cd ..
  7. cmake .

At this step, I encountered this error:

CMake Error: The following variables are used in this project, but they are set to NOTFOUND.
Please set them or make sure they are set and tested correctly in the CMake files:
CATCH_INCLUDE_DIR
used as include directory in directory /home/saratk/etcd-cpp-apiv3/tst

-- Configuring incomplete, errors occurred!
See also "/home/saratk/etcd-cpp-apiv3/CMakeFiles/CMakeOutput.log".

To workaround this problem, I opened CMakeCache.txt file in the same directory and changed the following line

CATCH_INCLUDE_DIR:PATH=CATCH_INCLUDE_DIR-NOTFOUND

to

CATCH_INCLUDE_DIR:PATH= and did "cmake ." again

  1. make

This step failed with following error

/etcd-cpp-apiv3/v3/src/Action.cpp: In member function ‘void etcdv3::Action::waitForResponse()’:
/etcd-cpp-apiv3/v3/src/Action.cpp:26:36: error: ‘GPR_ASSERT’ was not declared in this scope
GPR_ASSERT(got_tag == (void*)this);
^
make[2]: *** [src/CMakeFiles/etcd-cpp-api.dir/__/v3/src/Action.cpp.o] Error 1
make[1]: *** [src/CMakeFiles/etcd-cpp-api.dir/all] Error 2
make: *** [all] Error 2

To workaround, I opened the file Action.cpp and replaced GPR_ASSERT() with assert()

  1. make install

At this point header files got installed in /usr/local/include/etcd/ and the library itself got installed as /usr/local/lib/libetcd-cpp-api.so

  1. After this I still had to manually copy the header files generated from steps 4 and 5 to /usr/local/include/etcd/

A decent documentation with step-by-step instructions would be nice to begin with to encourage others to use this lib (assuming it works, I haven't come to that yet).

Thanks,
Sarat

etcd-cpp-apiv3 with TLS and certs

I am trying to find an equivalent way of accessing etcd server which has TLS and certs configured:

In case ETCD is configured with TLS certificates support:

ETCDCTL_API=3 etcdctl --endpoints <etcd_ip>:2379 --cacert <ca_cert_path> --cert <cert_path> --key <cert_key_path> get / --prefix --keys-only

I could find the info related to auth with user/password.

Can anyone suggest the equivalent API/function in etcd-cpp-apiv3?

windows 10 vcpkg build failed

at windows 10, i use vcpkg build , but it run failed.

this is cmd run info:

D:\third_party\vcpkg>vcpkg install etcd-cpp-apiv3
Computing installation plan...
The following packages will be built and installed:
etcd-cpp-apiv3[core]:x86-windows -> 0.2.1
Detecting compiler hash for triplet x86-windows...
Could not locate cached archive: C:\Users\z\AppData\Local\vcpkg\archives\72\7228f5c7dd3969b04f5f1fb8d94d3e38da16c05ca65a9571c842bcc760cebdb4.zip
Starting package 1/1: etcd-cpp-apiv3:x86-windows
Building package etcd-cpp-apiv3[core]:x86-windows...
-- Using D:/third_party/vcpkg/downloads/etcd-cpp-apiv3-etcd-cpp-apiv3-v0.2.1.tar.gz
-- Cleaning sources at D:/third_party/vcpkg/buildtrees/etcd-cpp-apiv3/src/v0.2.1-85b6180791.clean. Use --editable to skip cleaning for the packages you specify.
-- Extracting source D:/third_party/vcpkg/downloads/etcd-cpp-apiv3-etcd-cpp-apiv3-v0.2.1.tar.gz
-- Using source at D:/third_party/vcpkg/buildtrees/etcd-cpp-apiv3/src/v0.2.1-85b6180791.clean
-- Configuring x86-windows
-- Building x86-windows-dbg
CMake Error at scripts/cmake/vcpkg_execute_build_process.cmake:146 (message):
Command failed: D:/third_party/vcpkg/downloads/tools/cmake-3.20.2-windows/cmake-3.20.2-windows-i386/bin/cmake.exe --build . --config Debug --target install -- -v -j17
Working Directory: D:/third_party/vcpkg/buildtrees/etcd-cpp-apiv3/x86-windows-dbg
See logs for more information:
D:\third_party\vcpkg\buildtrees\etcd-cpp-apiv3\install-x86-windows-dbg-out.log

Call Stack (most recent call first):
scripts/cmake/vcpkg_build_cmake.cmake:104 (vcpkg_execute_build_process)
scripts/cmake/vcpkg_install_cmake.cmake:44 (vcpkg_build_cmake)
ports/etcd-cpp-apiv3/portfile.cmake:18 (vcpkg_install_cmake)
scripts/ports.cmake:139 (include)

Error: Building package etcd-cpp-apiv3:x86-windows failed with: BUILD_FAILED
Please ensure you're using the latest portfiles with .\vcpkg update, then
submit an issue at https://github.com/Microsoft/vcpkg/issues including:
Package: etcd-cpp-apiv3:x86-windows
Vcpkg version: 2021-05-05-e8977e69d9a3fb462d9ad42013d83a7682706659

Additionally, attach any relevant sections from the log files above.

list/delete/watch API on range.

The list/delete/watch gRPC API support range request (i.e., specifying the range_end field). In current implementation the range_end is generated by prefix, which is aligned with etcdctl. But we do could make it more flexible.

The feature is requested by the library user with the following case:

Is it possible to watch a “range” of keys? Ie. if we have keys from k1, k2, .... k9, k10, all under a prefix, would it be possible to create an etcd::Watcher class that only watches keys k1, k2, k3, ie in the range k1 - k3? Without watching the rest of the keys in this prefix?

Linker errors when building test executables

I am trying to use etcd-cpp-apiv3 on Linux to build a etcd client. I was testing with a sample application but it is failing at the linking stage. I get similar link errors when building etcd-cpp-apiv3 test executables too.

Truncated output:

...
[ 87%] Building CXX object tst/CMakeFiles/LockTest.dir/LockTest.cpp.o
[ 89%] Linking CXX executable ../bin/WatcherTest
[ 91%] Linking CXX executable ../bin/LockTest
[ 92%] Linking CXX executable ../bin/EtcdTest
�[91m/usr/bin/ld: /usr/local/lib/libgpr.a(log_linux.cc.o): in function `gpr_default_log(gpr_log_func_args*)':
log_linux.cc:(.text+0x1e1): undefined reference to `bool absl::lts_2020_02_25::str_format_internal::FormatArgImpl::Dispatch<char const*>(absl::lts_2020_02_25::str_format_internal::FormatArgImpl::Data, absl::lts_2020_02_25::str_format_internal::FormatConversionSpec, void*)'
/usr/bin/ld: log_linux.cc:(.text+0x1f0): undefined reference to `bool absl::lts_2020_02_25::str_format_internal::FormatArgImpl::Dispatch<int>(absl::lts_2020_02_25::str_format_internal::FormatArgImpl::Data, absl::lts_2020_02_25::str_format_internal::FormatConversionSpec, void*)'
/usr/bin/ld: log_linux.cc:(.text+0x268): undefined reference to `bool absl::lts_2020_02_25::str_format_internal::FormatArgImpl::Dispatch<long>(absl::lts_2020_02_25::str_format_internal::FormatArgImpl::Data, absl::lts_2020_02_25::str_format_internal::FormatConversionSpec, void*)'
/usr/bin/ld: log_linux.cc:(.text+0x27f): undefined reference to `absl::lts_2020_02_25::str_format_internal::FormatPack[abi:cxx11](absl::lts_2020_02_25::str_format_internal::UntypedFormatSpecImpl, absl::lts_2020_02_25::Span<absl::lts_2020_02_25::str_format_internal::FormatArgImpl const>)'
/usr/bin/ld: /usr/local/lib/libgpr.a(global_config_env.cc.o): in function `grpc_core::GlobalConfigEnvBool::Get()':
global_config_env.cc:(.text+0x196): undefined reference to `bool absl::lts_2020_02_25::str_format_internal::FormatArgImpl::Dispatch<char const*>(absl::lts_2020_02_25::str_format_internal::FormatArgImpl::Data, absl::lts_2020_02_25::str_format_internal::FormatConversionSpec, void*)'
/usr/bin/ld: global_config_env.cc:(.text+0x1cb): undefined reference to `absl::lts_2020_02_25::str_format_internal::FormatPack[abi:cxx11](absl::lts_2020_02_25::str_format_internal::UntypedFormatSpecImpl, absl::lts_2020_02_25::Span<absl::lts_2020_02_25::str_format_internal::FormatArgImpl const>)'
/usr/bin/ld: /usr/local/lib/libgpr.a(global_config_env.cc.o): in function `grpc_core::GlobalConfigEnvInt32::Get()':
global_config_env.cc:(.text+0x2ea): undefined reference to `bool absl::lts_2020_02_25::str_format_internal::FormatArgImpl::Dispatch<char const*>(absl::lts_2020_02_25::str_format_internal::FormatArgImpl::Data, absl::lts_2020_02_25::str_format_internal::FormatConversionSpec, void*)'
/usr/bin/ld: global_config_env.cc:(.text+0x31f): undefined reference to `absl::lts_2020_02_25::str_format_internal::FormatPack[abi:cxx11](absl::lts_2020_02_25::str_format_internal::UntypedFormatSpecImpl, absl::lts_2020_02_25::Span<absl::lts_2020_02_25::str_format_internal::FormatArgImpl const>)'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(client_context.cc.o): in function `grpc::ClientContext::set_call(grpc_call*, std::shared_ptr<grpc::Channel> const&)':
client_context.cc:(.text+0x332): undefined reference to `grpc_call_cancel_with_status'
/usr/bin/ld: client_context.cc:(.text+0x34b): undefined reference to `grpc_call_cancel'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(client_context.cc.o): in function `grpc::ClientContext::TryCancel()':
client_context.cc:(.text+0x452): undefined reference to `grpc_call_cancel'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(client_context.cc.o): in function `grpc::ClientContext::peer[abi:cxx11]() const':
client_context.cc:(.text+0x4b8): undefined reference to `grpc_call_get_peer'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(client_context.cc.o): in function `grpc::ClientContext::~ClientContext()':
client_context.cc:(.text+0x929): undefined reference to `grpc_call_unref'
...

I have prepared a Dockerfile to illustrate the exact steps I took.

(This shows only the steps upto building test executables. I believe the link errors I get for my sample application are the same).

I hope I am not doing something wrong as I was following the README (this guide also helped). I really appreciate if someone can have a look and advice a fix/workaround. Even though the complete recreation steps are in the Dockerfile, I would gladly provide more details if needed.

Ubuntu 18.04 Core dumped

I compiled etcd-cpp-apiv3 successfully on Ubuntu 18.04, I wrote a demo program according the Readme (just copyed and changed nothing), then I got an error:

Floating point exception (core dumped)

Yesterday on my Ubuntu 20.04, everything worked well and I got correct keys output. But in Ubuntu 18.04, client program didn't work. I alos changed my g++ to version 9.2 and no luck, still this error.

P.S. boost, grpc, protobuf and cpprest library I used Ubuntu apt repository lib, (libcpprest), didn't compile the git codes, does this matter?

So I confused, any help pls ?

The range_end setting for prefix match is incorrect

Per etcd document, to get all keys with a prefix, the range_end should be key plus one, (e.g., "aa"+1 == "ab", "a\xff"+1 == "b"). However, the below cpp api implementation is not handling the "a\xff" case properly:

14 std::string range_end(parameters.key);-
15 int ascii = (int)range_end[range_end.length()-1];
16 range_end.back() = ascii+1;

Compare it to the official Go clientv3 implementation:

func getPrefix(key []byte) []byte {
end := make([]byte, len(key))
copy(end, key)
for i := len(end) - 1; i >= 0; i-- {
if end[i] < 0xff {
end[i] = end[i] + 1
end = end[:i+1]
return end
}
}
// next prefix does not exist (e.g., 0xffff);
// default to WithFromKey policy
return noPrefixEnd
}

compile error with a demo

My codes:

#include <etcd/Response.hpp>
#include <iostream>
#include <etcd/Client.hpp>
using namespace std;

int main() {
    etcd::Client etcd("http://127.0.0.1:4001");
    etcd::Response response = etcd.get("abc").get();
    cout << response.value().as_string()<<endl;
}

And I compiled it through a CMakeLists.txt:

cmake_minimum_required(VERSION 3.16.3)
project(etcdclient)
include_directories("/usr/local/include/etcd")
include_directories("/usr/local/include/etcd/proto")
include_directories("/usr/local/include/etcd/v3")
include_directories("/home/myuser/etcd-cpp-apiv3")
add_executable(etcdclient main.cpp)
target_link_libraries(etcdclient grpc grpc++ cpprest ssl crypto boost_system boost_thread pthread)

Compile failed and I don't know what was missing, please give me some suggestions, thx.

Get lease ttl and connection state for lock and keep-alive

I'm trying to understand how to get the lease TTL for a lock, and also ideally a callback on lease expiry. This happens for example during a network partition or an etcd failover.

Specifically I would want the following:

  • Whether a lease has expired for a lock? (TTL = -1 on the keep-alive I believe)
  • Whether the keep-alive connection has failed?

All help much appreciated!

do we have an example of syn watch?

hi, all the watch operation examples are asynchronous, do we have a way to sync the watch?
I tried below, but it return right way, instead of block there.
thanks for any suggestions!

static std::string etcd_uri("http: 127.0.0.1:2379");
etcd::Client etcd(etcd_uri);
etcd::Response response;
pplx::tasketcd::Response response_task = etcd.watch("/test/key1",true);
try
{
std::cout << "start watch" << std::endl;
response = response_task.get(); // can throw
if (response.is_ok())
std::cout << "successful action is: "<<response.action()<<" value= " << response.value().as_string() << std::endl;
else
std::cout << "operation failed, details: " << response.error_message();
}
catch (std::exception const & ex)
{
std::cerr << "communication problem, details: " << ex.what();
}


result is as below:

start watch
successful action is: value=

[need to fix]

In README.md :

Keep alive
Keep alive for leases is implemented using a seperate class KeepAlive, which can be used as:

etcd::KeepAlive keepalive(etcd, lease_id, ttl); ==> change to: etcd::KeepAlive keepalive(etcd, ttl, lease_id);
It will perform a periodly keep-alive action before it is cancelled explicitly, or destructed implicitly.

In CMakeLists.txt
install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/Client.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/SyncClient.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/Response.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/Value.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/Watcher.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/KeepAlive.hpp # add
DESTINATION include/etcd)

Thx
最终依然需要感谢,提供了这个客户端。这段就中文了。再次感谢。

etcdv3::AsyncGetAction::AsyncGetAction always sort results

The etcdv3::AsyncGetAction::AsyncGetAction() method has hardcoded sort target of KEY and sort order of ASCEND. Can we make this optional? It may affect the "ls" performance on a large data set, if user doesn't care about ordering.

12 if(parameters.withPrefix)
13 {
14 std::string range_end(parameters.key);-
15 int ascii = (int)range_end[range_end.length()-1];
16 range_end.back() = ascii+1;
17
18 get_request.set_range_end(range_end);
19 get_request.set_sort_target(RangeRequest::SortTarget::RangeRequest_SortTarget_KEY);
20 get_request.set_sort_order(RangeRequest::SortOrder::RangeRequest_SortOrder_ASCEND);
21 }

etcd watcher API re-connect behavior

Hi there,

For the etcd watcher API in the etcd-cpp-apiv3 library, if the connection to etcd is down for a while, will the watcher auto reconnect in this case or is it application's responsibility to re-connect?

If it is application's responsibility to re-connect, is there any recommended way to detect such a connection failure for the watcher and re-connect?

I cannot find any doc in the repo describing the behavior for this, could you please shed some light on it? Thanks.

some question about etcd lock API behavior

according to he api implement in golang,seems need create a lease and keepalive it to make sure lock success or fail. what the behavior need be when program crash after a success locking. base on current code, it seems will cause whole distribution session by locked forever?

any reference can define those behavior? thanks for your great job for c++ implement again.

vcpkg install under macOS

I tried to install etcd-cpp-apiv3 via vcpkg (but under macOS instead of Windows), and ran into the same issue as #64

cd /Users/ss/dev/tools/vcpkg/buildtrees/etcd-cpp-apiv3/x64-osx-dbg/proto && /Users/ss/dev/tools/vcpkg/installed/x64-osx/tools/protobuf/protoc --cpp_out /Users/ss/dev/tools/vcpkg/buildtrees/etcd-cpp-apiv3/x64-osx-dbg/proto/gen/proto -I /Users/ss/dev/tools/vcpkg/buildtrees/etcd-cpp-apiv3/src/v0.2.1-85b6180791.clean/proto /Users/ss/dev/tools/vcpkg/buildtrees/etcd-cpp-apiv3/src/v0.2.1-85b6180791.clean/proto/auth.proto
google/protobuf/descriptor.proto: File not found.
gogoproto/gogo.proto:32:1: Import "google/protobuf/descriptor.proto" was not found or had errors.
gogoproto/gogo.proto:38:8: "google.protobuf.EnumOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.EnumOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.EnumOptions" is not defined.

I used the vcpkg portfile in this repo, and grpc/protobuf is installed into $VCPKG_ROOT/installed/x64-osx directory (descriptor.proto can be found under $VCPKG_ROOT/installed/x64-osx/include/google/protobuf), what is the recommended approach to add this path for the build? Thanks.

create a proper release

I would like to not clone master or pin my builds to the current HEAD (an arbitrary-looking SHA when you're looking at the build file). Please tag a release, preferably following semver

The demo you gave is wrong

Demo:

etcd::Client etcd("http://127.0.0.1:4001");
pplx::task<etcd::Response> response_task = etcd.get("/test/key1").get();
 // ... do something else
etcd::Response response = response_task.get();
std::cout << response.value().as_string();

Hi, as you give above, line two:

 1. etcd.get("keyname")  should return type pplx::task<etcd::Response>
 2. etcd.get("keyname").get()  should return type  etcd::Response 

So, the correct code of line two should be :

etcd::Response response_task = etcd.get("/test/key1").get();

can't build test examples

i try to build the test example,but it show lots of error info.like

core_codegen.cc:(.text+0x3e): undefined reference to `grpc_completion_queue_create'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::grpc_completion_queue_create_for_next(void*)':
core_codegen.cc:(.text+0x58): undefined reference to `grpc_completion_queue_create_for_next'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::grpc_completion_queue_create_for_pluck(void*)':
core_codegen.cc:(.text+0x68): undefined reference to `grpc_completion_queue_create_for_pluck'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::grpc_completion_queue_shutdown(grpc_completion_queue*)':
core_codegen.cc:(.text+0x78): undefined reference to `grpc_completion_queue_shutdown'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::grpc_completion_queue_destroy(grpc_completion_queue*)':
core_codegen.cc:(.text+0x88): undefined reference to `grpc_completion_queue_destroy'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::grpc_completion_queue_pluck(grpc_completion_queue*, void*, gpr_timespec, void*)':
core_codegen.cc:(.text+0xa4): undefined reference to `grpc_completion_queue_pluck'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::grpc_init()':
core_codegen.cc:(.text+0xd5): undefined reference to `grpc_init'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::grpc_shutdown()':
core_codegen.cc:(.text+0xe5): undefined reference to `grpc_shutdown'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::gpr_mu_init(long*)':
core_codegen.cc:(.text+0xf8): undefined reference to `gpr_mu_init'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::gpr_mu_destroy(long*)':
core_codegen.cc:(.text+0x108): undefined reference to `gpr_mu_destroy'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::gpr_mu_lock(long*)':
core_codegen.cc:(.text+0x118): undefined reference to `gpr_mu_lock'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::gpr_mu_unlock(long*)':
core_codegen.cc:(.text+0x128): undefined reference to `gpr_mu_unlock'
/usr/bin/ld: /usr/local/lib/libgrpc++.a(core_codegen.cc.o): in function `grpc::CoreCodegen::gpr_cv_init(long*)':

Range RPC Support

Any chance of adding support for the Range RPC? I see we already have the RangeResponse, but not the RangeRequest.

windows vcpkg not build lib!

I can't generate lib files using windows vcpkg, only dll files, target_link_libraries(test PRIVATE etcd-cpp-apiv3) cannot be linked!
A warning appears when generating:
If a directory should be populated but is not, this might indicate an error in the portfile.
If the directories are not needed and their creation cannot be disabled, use something like this in the portfile to remove them:

 file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/a/dir" "${CURRENT_PACKAGES_DIR}/some/other/dir")

core about watch

the core general in v3/src/AsyncWatchAction.cpp
stream->Write(watch_req, (void*)"write");

vcpkg build out etcd-cpp-api.dll when use, always load failed

i use QT to load the etcd-cpp-api.dll failed, i don't know why.
this is my QT code:

int etcd::Init() {
    QLibrary myLib(QStringLiteral("etcd-cpp-api.dll"));
    if(!myLib.load()){
        qDebug()<<"Load dll failed = " << myLib.errorString();
        return -1;
    }
    qDebug()<<"Load dll success!";
    return 0;
}

when i use relative path , QT exe
qDebug()<<"Load dll failed = " << myLib.errorString();
like this:
Load dll failed = "Cannot load library etcd-cpp-api.dll: 找不到指定的模块。"

when i use absolute path , QT exe
qDebug()<<"Load dll failed = " << myLib.errorString();
like this:
Load dll failed = "Cannot load library D:\work\1-code\1-QT\qtTools\bin\etcd-cpp-api.dll: 找不到指定的模块。"

Compilation fails with: google/api/http.pb.h: No such file or directory

I followed the instructions to build and install the etcd-cpp-apiv3 package on Ubuntu 18.04 following the steps:

Build and install completed successfully without errors.

After that I created a test program like this, following snippet from here

 ...
  etcd::Client etcd("http://127.0.0.1:2379");
  etcd::Response response = etcd.get("/test/key1").get();
  std::cout << response.value().as_string();
 ...

I try to compile like this:
g++ test.cpp -o test_etcd -lpthread -letcd-cpp-api -lprotobuf -lgrpc++ -lgrpc -lz -lcpprest -lssl -lcrypto -lboost_system

However, I get error like this:

In file included from /usr/local/include/etcd/proto/rpc.pb.h:34:0,
                 from /usr/local/include/etcd/proto/rpc.grpc.pb.h:7,
                 from /usr/local/include/etcd/Client.hpp:11,
                 from test.cpp:1:
/usr/local/include/etcd/proto/google/api/annotations.pb.h:28:10: fatal error: google/api/http.pb.h: No such file or directory
 #include "google/api/http.pb.h"
          ^~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

I already installed proto and grpc libs and expected it to work, can anyone suggest what is missing here?

google/protobuf/descriptor.proto: File not found

I make fail. output like this:

[  2%] Running cpp protocol buffer compiler on auth.proto
google/protobuf/descriptor.proto: File not found.
gogoproto/gogo.proto:32:1: Import "google/protobuf/descriptor.proto" was not found or had errors.
gogoproto/gogo.proto:38:8: "google.protobuf.EnumOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.EnumOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.EnumOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.EnumOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.EnumOptions" is not defined.
gogoproto/gogo.proto:46:8: "google.protobuf.EnumValueOptions" is not defined.
gogoproto/gogo.proto:50:8: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FileOptions" is not defined.
gogoproto/gogo.proto:92:8: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.MessageOptions" is not defined.
gogoproto/gogo.proto:129:8: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
auth.proto:4:1: Import "gogoproto/gogo.proto" was not found or had errors.
make[2]: *** [proto/gen/proto/auth.pb.h] Error 1
make[1]: *** [proto/CMakeFiles/protobuf_generates.dir/all] Error 2
make: *** [all] Error 2

and I build shell cmd like this:

pushd /tmp/
if [ ! -d /tmp/etcd-cpp-apiv3 ]; then
    git clone https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3.git
fi
cd etcd-cpp-apiv3
mkdir -p build
mkdir -p my_build_out
cd build
cmake -DCMAKE_INSTALL_PREFIX=$PWD/../my_build_out \
    -DCMAKE_BUILD_TYPE=Release \
    -DBOOST_INCLUDEDIR=/tmp/boost_1_76_0/my_build_out/include \
    -DBOOST_LIBRARYDIR=/tmp/boost_1_76_0/my_build_out/lib \
    -DProtobuf_INCLUDE_DIR=/tmp/grpc/third_party/protobuf/my_build_out/include \
    -DProtobuf_LIBRARIES=/tmp/grpc/third_party/protobuf/my_build_out/lib \
    -DGPR_LIBRARY=/tmp/grpc/cmake/build \
    -DGRPC_LIBRARY=/tmp/grpc/cmake/build \
    -DGRPC_INCLUDE_DIR=/tmp/grpc/include \
    -DGRPC_GRPC++_REFLECTION_LIBRARY=/tmp/grpc/cmake/build \
    -DGRPC_GRPC++_LIBRARY=/tmp/grpc/cmake/build \
    -DCPPREST_INCLUDE_DIR=/tmp/cpprestsdk/my_build_out/include \
    -DCPPREST_LIB=/tmp/cpprestsdk/my_build_out/lib \
    ..
make && make install
popd

fatal error: grpc++/grpc++.h: No such file or directory

[fananchong@vm-centos7 build]$ cmake -DCMAKE_INSTALL_PREFIX=$PWD/../my_build_out \
>     -DCMAKE_BUILD_TYPE=Release \
>     -DBOOST_INCLUDEDIR=/tmp/boost_1_74_0/my_build_out/include \
>     -DBOOST_LIBRARYDIR=/tmp/boost_1_74_0/my_build_out/lib \
>     -DProtobuf_INCLUDE_DIR=/tmp/grpc/third_party/protobuf/my_build_out/include \
>     -DProtobuf_LIBRARIES=/tmp/grpc/third_party/protobuf/my_build_out/lib/libprotobuf.a \
>     -DGPR_LIBRARY=/tmp/grpc/cmake/build/libgpr.a \
>     -DGRPC_LIBRARY=/tmp/grpc/cmake/build/libgrpc.a \
>     -DGRPC_INCLUDE_DIR=/tmp/grpc/include \
>     -DGRPC_GRPC++_REFLECTION_LIBRARY=/tmp/grpc/cmake/build/libgrpc++_reflection.a \
>     -DGRPC_GRPC++_LIBRARY=/tmp/grpc/cmake/build/libgrpc++.a \
>     -DCPPREST_INCLUDE_DIR=/tmp/cpprestsdk/my_build_out/include \
>     -DCPPREST_LIB=/tmp/cpprestsdk/my_build_out/lib \
>     ..
-- The C compiler identification is GNU 7.3.1
-- The CXX compiler identification is GNU 7.3.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /opt/rh/devtoolset-7/root/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /opt/rh/devtoolset-7/root/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Failed
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE  
-- Found Boost: /tmp/boost_1_74_0/my_build_out/include (found version "1.74.0") found components: system thread random chrono date_time atomic 
-- Found OpenSSL: /usr/lib64/libcrypto.so (found version "1.0.2k")  
-- Found Protobuf: /tmp/grpc/third_party/protobuf/my_build_out/lib/libprotobuf.a (found version "3.15.8") 
-- Found gRPC: /tmp/grpc/cmake/build/libgpr.a  
-- Performing Test GRPC_CHANNEL_CLASS_FOUND
-- Performing Test GRPC_CHANNEL_CLASS_FOUND - Failed
-- Found unit_test - AuthTest
-- Found unit_test - EtcdSyncTest
-- Found unit_test - EtcdTest
-- Found unit_test - LockTest
-- Found unit_test - SecurityChannelTest
-- Found unit_test - WatcherTest
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/etcd-cpp-apiv3/build
[fananchong@vm-centos7 build]$ make
[ 23%] Built target protobuf_generates
[ 25%] Building CXX object src/CMakeFiles/etcd-cpp-api.dir/Client.cpp.o
/tmp/etcd-cpp-apiv3/src/Client.cpp:23:10: fatal error: grpc++/grpc++.h: No such file or directory
 #include <grpc++/grpc++.h>
          ^~~~~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [src/CMakeFiles/etcd-cpp-api.dir/Client.cpp.o] Error 1
make[1]: *** [src/CMakeFiles/etcd-cpp-api.dir/all] Error 2
make: *** [all] Error 2

etcd_test fails with an assertion in gRPC

I had same problems during compilation as mentioned on #2

Fixed them by changing cmake file like this

prasad@prasad-Inspiron-5567:~/sources/nokia/etcd-cpp-apiv3$ git diff
diff --git a/tst/CMakeLists.txt b/tst/CMakeLists.txt
index e0ce264..53a515b 100644
--- a/tst/CMakeLists.txt
+++ b/tst/CMakeLists.txt
@@ -1,9 +1,12 @@
-find_path(CATCH_INCLUDE_DIR NAMES catch.hpp)
+find_path(CATCH_INCLUDE_DIR NAMES catch.hpp PATHS ${CMAKE_SOURCE_DIR})
 include_directories(${CATCH_INCLUDE_DIR})
 
 add_executable(etcd_test EtcdTest.cpp EtcdSyncTest.cpp WatcherTest.cpp)
 set_property(TARGET etcd_test PROPERTY CXX_STANDARD 11)
 
-target_link_libraries(etcd_test etcd-cpp-api)
+target_link_libraries(etcd_test
+       etcd-cpp-api
+       pthread
+)
 
 add_test(etcd_test etcd_test)

After making above changes the library compiles however, make test fails

root@ubuntu16:~/workspace/nokia/etcd-cpp-apiv3/build# make test
Running tests...
Test project /home/prasad/workspace/nokia/etcd-cpp-apiv3/build
    Start 1: etcd_test
1/1 Test #1: etcd_test ........................***Failed    0.76 sec

0% tests passed, 1 tests failed out of 1

Total Test time (real) =   0.78 sec

The following tests FAILED:
	  1 - etcd_test (Failed)
Errors while running CTest
Makefile:105: recipe for target 'test' failed
make: *** [test] Error 8

It seems like an assertion in gRPC failed.

root@ubuntu16:~/workspace/nokia/etcd-cpp-apiv3/build# ./tst/etcd_test  
E0522 13:22:31.865897860   10686 channel_cc.cc:136]          assertion failed: GRPC_CALL_OK == grpc_call_start_batch(call->call(), cops, nops, ops, nullptr)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
etcd_test is a Catch v1.5.4 host application.
Run with -? for options

-------------------------------------------------------------------------------
wait for a value change
-------------------------------------------------------------------------------
/home/prasad/workspace/nokia/etcd-cpp-apiv3/tst/EtcdTest.cpp:277
...............................................................................

/home/prasad/workspace/nokia/etcd-cpp-apiv3/tst/EtcdTest.cpp:277: FAILED:
due to a fatal error condition:
  SIGABRT - Abort (abnormal termination) signal

===============================================================================
test cases:  14 |  13 passed | 1 failed
assertions: 108 | 107 passed | 1 failed

import library in clion project using cmake on macOs

I installed the required dependencies via brew:
-boost 1.76.0
[email protected] 1.1.1k
-grpc 1.38.1
-protobuf 3.17.3

cpprestsdk didn't build (mac got unresponsive for a very long time)
so I installed cpprestsdk via brew as mentioned on their repo
-cpprestsdk 2.10.18

I built etcd-cpp-api-v3 and got the library installed
/usr/local/include/etcd/Client.hpp
/usr/local/include/etcd/KeepAlive.hpp
/usr/local/include/etcd/SyncClient.hpp
/usr/local/include/etcd/Response.hpp
/usr/local/include/etcd/Value.hpp
/usr/local/include/etcd/Watcher.hpp
/usr/local/include/etcd/kv.pb.h
/usr/local/include/etcd/v3/Transaction.hpp
/usr/local/include/etcd/v3/txn.pb.h
/usr/local/lib/cmake/etcd-cpp-api/FindGRPC.cmake
/usr/local/lib/cmake/etcd-cpp-api/etcd-cpp-api-config.cmake
/usr/local/lib/cmake/etcd-cpp-api/etcd-cpp-api-config-version.cmake
/usr/local/lib/cmake/etcd-cpp-api/etcd-targets.cmake
/usr/local/lib/cmake/etcd-cpp-api/etcd-targets-release.cmake
/usr/local/lib/libetcd-cpp-api.dylib

Then in my project I tried to get the library via "find_package(etcd-cpp-api)" but got this warning:

CMake Warning at CMakeLists.txt:22 (find_package):
Found package configuration file:

/usr/local/lib/cmake/etcd-cpp-api/etcd-cpp-api-config.cmake

but it set etcd-cpp-api_FOUND to FALSE so package "etcd-cpp-api" is
considered to be NOT FOUND.

also includes are not set correctly as I'm getting: 'etcd/Client.hpp' file not found

Floating point exception (core dumped) on running the compiled app

I compiled the etcd-cpp-apiv3 lib and below code following steps here: #37

Test Program:

cat test.cpp

#include <etcd/Client.hpp>
#include <etcd/Response.hpp>
#include <string>
#include <iostream>

int main()
{
  
  std::cout << "Hello";
  etcd::Client etcd("http://127.0.0.1:2379");
  etcd::Response response = etcd.get("/test/key1").get();
  std::cout << response.value().as_string();
  return 0;
}

Compile:

g++ test.cpp -o test_etcd -lpthread -letcd-cpp-api -lprotobuf -lgrpc++ -lgrpc -lz -lcpprest -lssl -lcrypto -lboost_system

Run:


 ./test_etcd 
Floating point exception (core dumped)

Event though the above key can be accessed using etcdctl:

ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 get /test/key1
/test/key1
value1

Any thoughts what might be missing/wrong?

Could NOT find gRPC (missing: GPR_LIBRARY GRPC_LIBRARY GRPC_GRPC++_REFLECTION_LIBRARY)

I compile in ubuntu environment according to the instructions of README, cmake. The following error appears, how can I solve it?

-- Boost version: 1.69.0 -- Found the following Boost libraries: -- system -- thread -- locale -- random -- chrono -- Could NOT find gRPC (missing: GPR_LIBRARY GRPC_LIBRARY GRPC_GRPC++_REFLECTION_LIBRARY) CMake Error: The following variables are used in this project, but they are set to NOTFOUND. Please set them or make sure they are set and tested correctly in the CMake files: GPR_LIBRARY linked by target "etcd-cpp-api" in directory /root/etcd-cpp-apiv3/src GRPC_GRPC++_LIBRARY (ADVANCED) linked by target "etcd-cpp-api" in directory /root/etcd-cpp-apiv3/src GRPC_LIBRARY (ADVANCED) linked by target "etcd-cpp-api" in directory /root/etcd-cpp-apiv3/src

got core dumps when keepalive deconstruct

I got core dumps when keepalive deconstruct

#include "util/election.h"
#include <thread>
#include "gtest/gtest.h"
TEST(ELECTION, ELECTION1) {
  try{
  etcd::Client etcd("http://127.0.0.1:2379");
  auto lease_resp = etcd.leasegrant(3).get();
  auto _lease_id = lease_resp.value().lease();
  auto _keep_alive = std::unique_ptr<etcd::KeepAlive>(new etcd::KeepAlive(etcd, 3, _lease_id));
  etcd.set("ass", "ccc", _lease_id).get();
  }  catch ( std::exception& ex ) {
    std::cout << ex.what() << std::endl;
  } catch(...){
  }
}

the stack
(gdb) bt
#0 0x00000001003e0003 in ?? ()
#1 0x000000000042c804 in boost::asio::detail::service_registry::shutdown_services (this=0x7f38dfffeb58) at /usr/local/include/boost/asio/detail/impl/service_registry.ipp:44
#2 0x000000000042c903 in boost::asio::execution_context::shutdown (this=0x232c080) at /usr/local/include/boost/asio/impl/execution_context.ipp:41
#3 0x000000000042c8a7 in boost::asio::execution_context::~execution_context (this=0x232c080, __in_chrg=) at /usr/local/include/boost/asio/impl/execution_context.ipp:34
#4 0x0000000000439586 in boost::asio::io_context::~io_context (this=0x232c080, __in_chrg=) at /tmp/tmp.FKwox9TpTf/include/boost/asio/impl/io_context.ipp:56
#5 0x00007f38fe4e91b2 in etcd::KeepAlive::~KeepAlive (this=0x232c030, __in_chrg=) at external/etcd_cpp_apiv3/src/KeepAlive.cpp:101
#6 0x000000000042f1b4 in std::default_deleteetcd::KeepAlive::operator() (this=0x7ffffa55f6c0, __ptr=0x232c030) at /usr/lib/gcc/x86_64-unknown-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/unique_ptr.h:76
#7 0x000000000042e383 in std::unique_ptr<etcd::KeepAlive, std::default_deleteetcd::KeepAlive >::~unique_ptr (this=0x7ffffa55f6c0, __in_chrg=) at /usr/lib/gcc/x86_64-unknown-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/unique_ptr.h:236
#8 0x0000000000429882 in ELECTION_ELECTION1_Test::TestBody (this=0x2306df0) at util/election_test.cpp:13
#9 0x00007f38fe6438d4 in testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void> (object=0x2306df0, method=&virtual testing::Test::TestBody(), location=0x7f38fe6535eb "the test body") at external/googletest/googletest/src/gtest.cc:2433
#10 0x00007f38fe63eed5 in testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void> (object=0x2306df0, method=&virtual testing::Test::TestBody(), location=0x7f38fe6535eb "the test body") at external/googletest/googletest/src/gtest.cc:2469
#11 0x00007f38fe628698 in testing::Test::Run (this=0x2306df0) at external/googletest/googletest/src/gtest.cc:2509
#12 0x00007f38fe629009 in testing::TestInfo::Run (this=0x230d9c0) at external/googletest/googletest/src/gtest.cc:2684
#13 0x00007f38fe6296f0 in testing::TestSuite::Run (this=0x230d350) at external/googletest/googletest/src/gtest.cc:2816
#14 0x00007f38fe6355bb in testing::internal::UnitTestImpl::RunAllTests (this=0x230d0f0) at external/googletest/googletest/src/gtest.cc:5338
#15 0x00007f38fe6447d1 in testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool> (object=0x230d0f0, method=(bool (testing::internal::UnitTestImpl::)(testing::internal::UnitTestImpl * const)) 0x7f38fe635190 testing::internal::UnitTestImpl::RunAllTests(),
location=0x7f38fe653f88 "auxiliary test code (environments or event listeners)") at external/googletest/googletest/src/gtest.cc:2433
#16 0x00007f38fe63fee3 in testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool> (object=0x230d0f0, method=(bool (testing::internal::UnitTestImpl::
)(testing::internal::UnitTestImpl * const)) 0x7f38fe635190 testing::internal::UnitTestImpl::RunAllTests(),
location=0x7f38fe653f88 "auxiliary test code (environments or event listeners)") at external/googletest/googletest/src/gtest.cc:2469
#17 0x00007f38fe633ebe in testing::UnitTest::Run (this=0x7f38fe688440 testing::UnitTest::GetInstance()::instance) at external/googletest/googletest/src/gtest.cc:4925
#18 0x00007f38fee04b3e in RUN_ALL_TESTS () at external/googletest/googletest/include/gtest/gtest.h:2473
#19 0x00007f38fee04acd in main (argc=1, argv=0x7ffffa560078) at external/googletest/googlemock/src/gmock_main.cc:63
#20 0x00007f38fb68eb35 in __libc_start_main () from /lib64/libc.so.6
#21 0x0000000000429399 in _start ()

When the etcd server shuts down, the keepalive process crash

code is :

void CfgEtcd::KeepAlive()
{
    // key: /services/cserver/gouhuo:127.0.0.1:cserver2
    auto key = fmt::format("/services/{}{}:{}:{}", server_type_name, m_cfg_root, Get("", CFG_ITEM_listenHost, "0.0.0.0"), m_serverid);
    // value: 127.0.0.1:11220
    auto value = fmt::format("{}:{}", Get("", CFG_ITEM_listenHost, "0.0.0.0"), Get("", CFG_ITEM_listenPort, "10000"));
    etcd::Response resp = m_etcd_cli->leasegrant(1).get();
    auto lease_id = resp.value().lease();
    m_etcd_cli->set(key, value, lease_id);
    m_keepalive = std::make_shared<etcd::KeepAlive>(
        *m_etcd_cli, [](std::exception_ptr eptr)
        {
            try
            {
                if (eptr)
                {
                    std::rethrow_exception(eptr);
                }
            }
            catch (const std::runtime_error &e)
            {
                ERROR("[etcd] Connection failure: {}", e.what());
            }
            catch (const std::out_of_range &e)
            {
                ERROR("[etcd] Lease expiry : {}", e.what());
            }
        },
        1, lease_id);
}

when I close etcd server . the process crash .

this is log:

[etcd] Connection failure: Failed to refresh lease: error code: 10, message: Failed to create a lease keep-alive connection
terminate called after throwing an instance of 'std::system_error'
  what():  Resource deadlock avoided

Could NOT find OpenSSL

some machine have not this
if add
sudo apt install libssl-dev in readme file
that is better!
image

watching at a compacted revision doesn't gets canceled

Per etcd api document, watching at a compacted revision should get a response from etcd server that this watch has been canceled. However, this cpp api is not honoring that and will continue to watch for responses, and will get stuck in case of a synchronous call. The problem occur at two code places.

  1. etcdv3::AsyncWatchAction::waitForResponse(), for the "watch" method of etcd::Client. When watching at a compacted revision, it will get a response with latest revision in header (which is greater than parameters.revision), empty events, and "canceled" field set to true. This will fall to the else case of line 86, and gets stuck there because etcd server will not send any further response.

Expected behavior: it should check for reply.canceled() before line 66 , and cancel the watch if it is set to true.

64 if(got_tag == (void*)this) // read tag
65 {
66 if ((reply.created() && reply.header().revision() < parameters.revision) ||
67 reply.events_size() > 0) {
68 // we stop watch under two conditions:
69 //
70 // 1. watch for a future revision, return immediately with empty events set
71 // 2. receive any effective events.
72 isCancelled = true;
73 stream->WritesDone((void*)"writes done");
74 grpc::Status status;
75 stream->Finish(&status, (void )this);
76 cq_.Shutdown();
77
78 // leave a warning if the response is too large and been fragmented
79 if (reply.fragment()) {
80 std::cerr << "WARN: The response hasn't been fully received and parsed" << std::endl;
81 }
82 }
83 else
84 {
85 // otherwise, start next round read-reply
86 stream->Read(&reply, (void
)this);
87 }-
88 }

  1. etcdv3::AsyncWatchAction::waitForResponse(std::function<void(etcd::Response)> callback), for the watcher class. Watching at a compacted revision will return a response with no events. Thus, similar to above case, it will not handle this response properly, and will keep trying to read from stream.

Expected behavior: it should check for reply.canceled() before line 128 , and cancel the watch if it is set to true. Additionally, the watcher class needs a way to know if it has been canceled by the server. Consider adding following method to the watcher class:
bool etcd::Watcher::Canceled()
{
return call->Cancelled();
}

Also, the Cancelled() method needs to grab lock before accessing isCancelled:
bool etcdv3::AsyncWatchAction::Cancelled() const {

  • std::lock_guardstd::mutex scope_lock(this->protect_is_cancalled);
    return isCancelled;
    }

126 else if(got_tag == (void*)this) // read tag
127 {
128 if(reply.events_size())
129 {
130 // for the callback case, we don't stop immediately if watching for a future revison,
131 // we wait until there are some expected events.
132 auto resp = ParseResponse();
133 auto duration = std::chrono::duration_caststd::chrono::microseconds(
134 std::chrono::high_resolution_clock::now() - start_timepoint);
135 callback(etcd::Response(resp, duration));-
136 start_timepoint = std::chrono::high_resolution_clock::now();
137 }
138 stream->Read(&reply, (void*)this);
139 }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.