diff --git a/README.md b/README.md index 24b281d..0d9b3cf 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Github when you encounter problems when working with etcd 3.x releases. ## Usage ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + etcd::Client etcd("http://127.0.0.1:2379"); etcd::Response response = etcd.get("/test/key1").get(); std::cout << response.value().as_string(); ``` @@ -96,7 +96,7 @@ are blocking until the HTTP response arrives or some error situation happens. `g also returns the `etcd::Response` object. ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + etcd::Client etcd("http://127.0.0.1:2379"); pplx::task response_task = etcd.get("/test/key1"); // ... do something else etcd::Response response = response_task.get(); @@ -111,7 +111,7 @@ callback should be either a `etcd::Response` or a `pplx::task`. Y probably use a C++ lambda function here as a callback. ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + etcd::Client etcd("http://127.0.0.1:2379"); etcd.get("/test/key1").then([](etcd::Response response) { std::cout << response.value().as_string(); @@ -127,7 +127,7 @@ how you can catch the errors generated by the REST interface. The `get()` call w this case since the response has been already arrived (we are inside the callback). ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + etcd::Client etcd("http://127.0.0.1:2379"); etcd.get("/test/key1").then([](pplx::task response_task) { try @@ -238,7 +238,7 @@ transport security using openssl. We also provide a tool [`setup-ca.sh`](./security-config/setup-ca.sh) as a helper for development and testing. -#### transport security & Multiple endpoints +#### 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 @@ -271,6 +271,36 @@ And pass a `target_name_override` arguments to `WithSSL`, For more discussion about this feature, see also [#87](https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/issues/87), [grpc#20186](https://github.com/grpc/grpc/issues/20186) and [grpc#22119](https://github.com/grpc/grpc/issues/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., + +```cpp + 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](https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/issues/103). + ### Reading a value You can read a value with the `get()` method of the client instance. The only parameter is the @@ -290,7 +320,7 @@ key. You can also get the index number of the creation and the last modification the `created_index()` and the `modified_index()` methods. ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + etcd::Client etcd("http://127.0.0.1:2379"); pplx::task response_task = etcd.get("/test/key1"); try @@ -317,7 +347,7 @@ what was the previous value that this operation was overwritten. You can do that `prev_value()` method of the response object. ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + etcd::Client etcd("http://127.0.0.1:2379"); pplx::task response_task = etcd.set("/test/key1", "42"); try @@ -407,7 +437,7 @@ keys defined by the prefix. mkdir method is removed since etcdv3 treats everythi returns with the i-th element of the values vector, so `response.values()[i] == response.value(i)`. ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + 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) { @@ -424,7 +454,7 @@ prefix will be deleted. All deleted keys will be placed in `response.values()` a `response.keys()`. This parameter defaults to `false`. ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + 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"); @@ -444,7 +474,7 @@ The key supplied will NOT be treated as a prefix and will be treated as a normal Etcd lock has been supported as follows: ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + etcd::Client etcd("http://127.0.0.1:2379"); etcd.lock("/test/lock"); ``` @@ -454,7 +484,7 @@ the lock is unlocked. Users can also feed their own lease directory for lock: ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + etcd::Client etcd("http://127.0.0.1:2379"); etcd.lock_with_lease("/test/lock", lease_id); ``` @@ -575,7 +605,7 @@ Moreover, user can attached the lease to a key(s) by indicating the lease id in server will be indicated in `ttl()`. ```c++ - etcd::Client etcd("http://127.0.0.1:4001"); + 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(); diff --git a/etcd/Client.hpp b/etcd/Client.hpp index 60e4bad..3cc0763 100644 --- a/etcd/Client.hpp +++ b/etcd/Client.hpp @@ -23,10 +23,12 @@ namespace etcdv3 { #if defined(WITH_GRPC_CHANNEL_CLASS) namespace grpc { class Channel; + class ChannelArguments; } #else namespace grpc_impl { class Channel; + class ChannelArguments; } #endif @@ -56,6 +58,21 @@ namespace etcd Client(std::string const & etcd_url, std::string const & load_balancer = "round_robin"); + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param arguments user provided grpc channel arguments. + */ + Client(std::string const & etcd_url, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ); + /** * Constructs an etcd client object. * @@ -64,7 +81,22 @@ namespace etcd * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. */ static etcd::Client *WithUrl(std::string const & etcd_url, - std::string const & load_balancer = "round_robin"); + std::string const & load_balancer = "round_robin"); + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param arguments user provided grpc channel arguments. + */ + static etcd::Client *WithUrl(std::string const & etcd_url, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ); /** * Constructs an etcd client object. @@ -80,6 +112,26 @@ namespace etcd std::string const & password, std::string const & load_balancer = "round_robin"); + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param username username of etcd auth + * @param password password of etcd auth + * @param arguments user provided grpc channel arguments. + */ + Client(std::string const & etcd_url, + std::string const & username, + std::string const & password, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ); + + /** * Constructs an etcd client object. * @@ -90,9 +142,29 @@ namespace etcd * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. */ static etcd::Client *WithUser(std::string const & etcd_url, - std::string const & username, - std::string const & password, - std::string const & load_balancer = "round_robin"); + std::string const & username, + std::string const & password, + std::string const & load_balancer = "round_robin"); + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param username username of etcd auth + * @param password password of etcd auth + * @param arguments user provided grpc channel arguments. + */ + static etcd::Client *WithUser(std::string const & etcd_url, + std::string const & username, + std::string const & password, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ); + /** * Constructs an etcd client object. @@ -111,6 +183,29 @@ namespace etcd std::string const & target_name_override, std::string const & load_balancer); + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param ca root CA file for SSL/TLS connection. + * @param cert cert chain file for SSL/TLS authentication, could be empty string. + * @param key private key file for SSL/TLS authentication, could be empty string. + * @param arguments user provided grpc channel arguments. + */ + Client(std::string const & etcd_url, + std::string const & ca, + std::string const & cert, + std::string const & key, + std::string const & target_name_override, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments +#else + grpc_impl::ChannelArguments const & arguments +#endif + ); + + /** * Constructs an etcd client object. * @@ -125,11 +220,35 @@ namespace etcd * @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. */ static etcd::Client *WithSSL(std::string const & etcd_url, - std::string const & ca, - std::string const & cert = "", - std::string const & key = "", - std::string const & target_name_override = "", - std::string const & load_balancer = "round_robin"); + std::string const & ca, + std::string const & cert = "", + std::string const & key = "", + std::string const & target_name_override = "", + std::string const & load_balancer = "round_robin"); + + /** + * Constructs an etcd client object. + * + * @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", + * or multiple url, seperated by ',' or ';'. + * @param ca root CA file for SSL/TLS connection. + * @param cert cert chain file for SSL/TLS authentication, could be empty string. + * @param key private key file for SSL/TLS authentication, could be empty string. + * @param target_name_override Override the target host name if you want to pass multiple address + * for load balancing with SSL, and there's no DNS. The @target_name_override@ must exist in the + * SANS of your SSL certificate. + * @param arguments user provided grpc channel arguments. + */ + static etcd::Client *WithSSL(std::string const & etcd_url, +#if defined(WITH_GRPC_CHANNEL_CLASS) + grpc::ChannelArguments const & arguments, +#else + grpc_impl::ChannelArguments const & arguments, +#endif + std::string const & ca, + std::string const & cert = "", + std::string const & key = "", + std::string const & target_name_override = ""); /** * Get the HEAD revision of the connected etcd server. diff --git a/src/Client.cpp b/src/Client.cpp index 15a20ef..94c4400 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -200,11 +200,36 @@ etcd::Client::Client(std::string const & address, stubs->electionServiceStub = Election::NewStub(this->channel); } +etcd::Client::Client(std::string const & address, + grpc::ChannelArguments const & arguments) +{ + // create channels + std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); + grpc::ChannelArguments grpc_args = arguments; + grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); + grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); + std::shared_ptr creds = grpc::InsecureChannelCredentials(); + this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); + + // create stubs + stubs.reset(new EtcdServerStubs{}); + stubs->kvServiceStub = KV::NewStub(this->channel); + stubs->watchServiceStub= Watch::NewStub(this->channel); + stubs->leaseServiceStub= Lease::NewStub(this->channel); + stubs->lockServiceStub = Lock::NewStub(this->channel); + stubs->electionServiceStub = Election::NewStub(this->channel); +} + etcd::Client *etcd::Client::WithUrl(std::string const & etcd_url, std::string const & load_balancer) { return new etcd::Client(etcd_url, load_balancer); } +etcd::Client *etcd::Client::WithUrl(std::string const & etcd_url, + grpc::ChannelArguments const & arguments) { + return new etcd::Client(etcd_url, arguments); +} + etcd::Client::Client(std::string const & address, std::string const & username, std::string const & password, @@ -235,6 +260,35 @@ etcd::Client::Client(std::string const & address, stubs->electionServiceStub = Election::NewStub(this->channel); } +etcd::Client::Client(std::string const & address, + std::string const & username, + std::string const & password, + grpc::ChannelArguments const & arguments) +{ + // create channels + std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); + grpc::ChannelArguments grpc_args = arguments; + grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); + grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); + std::shared_ptr creds = grpc::InsecureChannelCredentials(); + this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); + + // auth + std::string token_or_message; + if (!etcd::detail::authenticate(this->channel, username, password, token_or_message)) { + throw std::invalid_argument("Etcd authentication failed: " + token_or_message); + } + this->auth_token = token_or_message; + + // setup stubs + stubs.reset(new EtcdServerStubs{}); + stubs->kvServiceStub = KV::NewStub(this->channel); + stubs->watchServiceStub= Watch::NewStub(this->channel); + stubs->leaseServiceStub= Lease::NewStub(this->channel); + stubs->lockServiceStub = Lock::NewStub(this->channel); + stubs->electionServiceStub = Election::NewStub(this->channel); +} + etcd::Client *etcd::Client::WithUser(std::string const & etcd_url, std::string const & username, std::string const & password, @@ -242,6 +296,14 @@ etcd::Client *etcd::Client::WithUser(std::string const & etcd_url, return new etcd::Client(etcd_url, username, password, load_balancer); } +etcd::Client *etcd::Client::WithUser(std::string const & etcd_url, + std::string const & username, + std::string const & password, + grpc::ChannelArguments const & arguments) { + return new etcd::Client(etcd_url, username, password, arguments); +} + + etcd::Client::Client(std::string const & address, std::string const & ca, std::string const & cert, @@ -271,6 +333,34 @@ etcd::Client::Client(std::string const & address, stubs->electionServiceStub = Election::NewStub(this->channel); } +etcd::Client::Client(std::string const & address, + std::string const & ca, + std::string const & cert, + std::string const & key, + std::string const & target_name_override, + grpc::ChannelArguments const & arguments) +{ + // create channels + std::string const addresses = etcd::detail::strip_and_resolve_addresses(address); + grpc::ChannelArguments grpc_args = arguments; + grpc_args.SetMaxSendMessageSize(std::numeric_limits::max()); + grpc_args.SetMaxReceiveMessageSize(std::numeric_limits::max()); + std::shared_ptr creds = grpc::SslCredentials( + etcd::detail::make_ssl_credentials(ca, cert, key)); + if (!target_name_override.empty()) { + grpc_args.SetString(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, target_name_override); + } + this->channel = grpc::CreateCustomChannel(addresses, creds, grpc_args); + + // setup stubs + stubs.reset(new EtcdServerStubs{}); + stubs->kvServiceStub = KV::NewStub(this->channel); + stubs->watchServiceStub= Watch::NewStub(this->channel); + stubs->leaseServiceStub= Lease::NewStub(this->channel); + stubs->lockServiceStub = Lock::NewStub(this->channel); + stubs->electionServiceStub = Election::NewStub(this->channel); +} + etcd::Client *etcd::Client::WithSSL(std::string const & etcd_url, std::string const & ca, std::string const & cert, @@ -280,6 +370,15 @@ etcd::Client *etcd::Client::WithSSL(std::string const & etcd_url, return new etcd::Client(etcd_url, ca, cert, key, target_name_override, load_balancer); } +etcd::Client *etcd::Client::WithSSL(std::string const & etcd_url, + grpc::ChannelArguments const & arguments, + std::string const & ca, + std::string const & cert, + std::string const & key, + std::string const & target_name_override) { + return new etcd::Client(etcd_url, ca, cert, key, target_name_override, arguments); +} + pplx::task etcd::Client::head() { etcdv3::ActionParameters params;