Compare commits

..

144 Commits

Author SHA1 Message Date
holobay 5994b54c68 Bump up the version to v0.15.5 2025-04-01 23:16:01 +03:00
Tao He ba6216385f Bump up the version to v0.15.4
Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
2023-12-20 18:28:15 +08:00
Tao He 5ccaccec43
Enable ipv6 endpoints support (#262)
Resolves #250

Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
2023-12-20 17:56:18 +08:00
Tao He b82efea7a9
Enable -fno-exceptions support (#261)
Resolves #259

Signed-off-by: Tao He <sighingnow@gmail.com>
2023-12-20 09:18:10 +08:00
Tao He 5aff57cce5
Fixes the noisy logs when meets invalid addresses. (#260)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-12-20 00:32:00 +08:00
penfree 84343ca9f0
Fix: keepalive exit without any message due to clock drift (#258)
Fix:  #257

Co-authored-by: qiupengfei <qiupengfei@baidu.com>
2023-12-19 23:45:19 +08:00
Diskein 59635008c0
Fixes compiler errors (#254)
Co-authored-by: Denis Kalantaevsky <dkalantaevsky@gmail.com>
Co-authored-by: Tao He <sighingnow@gmail.com>
2023-10-05 22:59:06 -05:00
Clément Péron 47f0d9e032
cmake: fix when cross compiling (#252)
To compile protobuf, CMake needs to use the protoc and grpc-cpp-plugin
in the host architecture.

Unfortunately by default the protoc and grpc-cpp-plugin are the one for
the Target.
And since gRPC 1.52 they are explictly not exported when Cross Compiling
to avoid architecture mismatch.
See:
831d2a6855

Fix this by looking at the correct program

See example.
https://github.com/grpc/grpc/blob/master/examples/cpp/cmake/common.cmake#L54-L62

Signed-off-by: Clément Péron <peron.clem@gmail.com>
2023-09-25 09:49:24 +08:00
Tao He 6fc1f164c0
Fixes the extra-smi error in code generated by protobuf (#251)
Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
2023-09-19 20:42:49 +08:00
Tao He e31ac4d4ca Bump up etcd-cpp-apiv3 to v0.15.3
Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
2023-07-27 11:28:00 +08:00
JonLiu1993 e5dc903a5d
Fix error LNK1107 and undeclared identifier 'IPPROTO_TCP' (#244)
When I [Update](https://github.com/microsoft/vcpkg/pull/32747)
etcd-cpp-apiv3 version from 0.14.2 to 0.15.2, I get two build error:
````
fatal error LNK1107: invalid or corrupt file: cannot read at 0x330
````
````
error: use of undeclared identifier 'IPPROTO_TCP'
````

The first error was because the target `etcd-cpp-api-core-objects`
linked the wrong `libprotobufd.dll` file instead of the .lib file, I
used the usage provided by vcpkg to link the correct .lib file to fix
this error.

Another error was because `IPPROTO_TCP` was missing declaration
`"<netinet/in.h>"`, I added it to fix this error.

---------

Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
Co-authored-by: Zhao Liu <v-zhli17@microsoft.com>
Co-authored-by: Tao He <linzhu.ht@alibaba-inc.com>
2023-07-27 11:27:19 +08:00
Tao He 0eee75b52e
KeepAlive: auto grant a new lease if 0 is given as lease id (#242)
Fixes #3037

Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
2023-07-20 14:21:27 +08:00
Tao He 15c022e36c Bump up etcd-cpp-apiv3 to v0.15.2
Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
2023-07-17 10:44:06 +08:00
Tao He 3d344190d7
Fixes txn delete response to keep backwards compatibility (#239)
Signed-off-by: Tao He <linzhu.ht@alibaba-inc.com>
2023-07-17 10:43:19 +08:00
Tao He 2c0d824ebe Bump up etcd-cpp-apiv3 to v0.15.1
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-07-15 20:23:01 +08:00
Tao He 068f37ba5c
Fixes the watcher cannot be cancelled issue with etcd 3.x (#238)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-07-15 20:00:16 +08:00
Tao He 153546f965
Refactor the implementation of etcd transactions. (#236)
Fixes #234.

Signed-off-by: Tao He <sighingnow@gmail.com>
2023-07-15 17:07:34 +08:00
Tao He 204038c4bc Fixes format issues
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-07-01 21:03:20 +08:00
Tao He d57dff2f86 Bump up etcd-cpp-apiv3 to v0.15.0
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-07-01 20:50:22 +08:00
Tao He 1d5128a7e8
Format source code using clformat (#233)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-07-01 20:49:16 +08:00
Tao He fe9f17e61e
Fixes a possible bug about watcher's id (#232)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-07-01 18:41:33 +08:00
Tao He 32fae70113
Fixes the implementation of Observe() (#231)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-07-01 13:30:43 +08:00
Tao He 09f665fe3e Format readme.md
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-06-30 15:37:27 +08:00
Tao He fcc5807748 Fixes a typo in README (fixes #229).
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-06-30 15:34:54 +08:00
Tao He dd2c0276aa
Find protobuf in CONFIG mode to fixes the absl dependencies. (#225)
See also: https://github.com/protocolbuffers/protobuf/issues/12292.

Signed-off-by: Tao He <sighingnow@gmail.com>
2023-06-15 13:47:39 +08:00
Tao He a8d5980c76
Include watch_id in the response (#223)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-05-16 09:45:51 +08:00
Tao He 0ed7bee2c8
Merge the .hpp/.cpp into one to optimize build time (#220)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-05-11 14:09:41 +08:00
Tao He e771d2f6da
Drop the boost dependency on the sync runtime (#216)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-04-28 20:38:13 +08:00
Tao He 5e2884f362 Bump up etcd-cpp-apiv3 to v0.14.3
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-04-27 11:07:10 +08:00
Tao He ebf9c493f1
Get and list keys with specified revision. (#215)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-04-26 21:06:23 +08:00
Tao He a288eb5db4 Add the test case for issue #212
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-04-21 16:53:19 +08:00
Tao He 24b1414118 Ubuntu 18.04 is no longer available on Github actions
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-04-20 21:16:00 +08:00
Tao He 91c64e18d3 The grpc_cpp_plugin may failed to be found, and leads to conflicts
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-04-20 20:42:10 +08:00
Tao He af0c96f6ba
The grpc_cpp_plugin may failed to be found (#211)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-04-20 19:50:27 +08:00
Tao He 6e4b45f986 Bump up the version to v0.14.2 2023-03-19 11:32:49 +08:00
Tao He c72e072f77
Add an option `BUILD_ETCD_CORE_ONLY=ON/OFF` to select the runtime (#208)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-19 11:31:57 +08:00
Tao He 16a9638e3e Fixes a markup error in README.md
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-15 10:12:59 +08:00
Tao He 66cf911654 Bump up the version to v0.14.1 as a bugfix release
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-14 20:41:03 +08:00
Tao He 9fc0f37ef7
Fixes a bug in lease action when grpc timeout is set (#204)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-14 20:37:34 +08:00
Tao He 5e27ac33c6
Fixes the segmentation fault error in watcher (#206)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-14 20:36:53 +08:00
Tao He f0f9c4e8c2
Enhance the campaign test and document the behaviour when timeout is set (#205)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-14 19:43:37 +08:00
Tao He d27f0b9e81 Bump up the version to v0.14.0
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-10 15:06:11 +08:00
Tao He cad42fdf07
Fixes the wrong key setup in watcher (#201)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-10 15:05:28 +08:00
Tao He 639c7e9f24 Fixes the wrong tag name by forceing the release version
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-08 11:57:25 +08:00
Tao He c23845ee90 Bump up the version to v0.2.14
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-08 10:26:49 +08:00
Tao He 9d3f8cec3d
Fixes bugs in ls/rmdir/watch for processing range end. (#199)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-07 21:47:10 +08:00
Tao He b12fc293b9
Fixes memory leak issue inside the watcher (#197)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-05 23:08:10 +08:00
Tao He 80b4d2178f Don't refer this pointer inside the detached thread
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-03-05 01:35:21 +08:00
Tao He f774f832de
Test etcd client from forked child process (#196)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-02-19 14:04:54 +08:00
Tao He 817153bcc9
Improve the cmake script for better compatibility (#195)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-02-19 11:14:05 +08:00
Tao He 3133fbec21
Fixes the Check() error when if next refresh is not triggered yet (#193)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-02-15 17:14:48 +08:00
Tao He ceb1af1110 Bump up version to v0.2.13
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-02-14 21:20:35 +08:00
Tao He 9b5c5bd3c6
Fixes the deadlock in keep alive (#191)
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-02-14 21:20:12 +08:00
Tao He 81d446e55c Remove in-repo vcpkg configurations
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-02-08 12:52:24 +08:00
Tao He 9e1e60af2e Bump up etcd-cpp-apiv3 to v0.2.12
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-02-01 11:52:38 +08:00
Rui Chen 5c7e155c9e
Add an cmake option which respect `CMAKE_CXX_STANDARD` for cxx standard. (#188) 2023-02-01 11:51:38 +08:00
Tao He 74ca58fdcf Bump up version to v0.2.11.
Signed-off-by: Tao He <sighingnow@gmail.com>
2023-01-31 12:54:15 +08:00
Rui Chen 04d5659f5f
ci: update action runners (#186)
* ci: add macos-11.0
* ci: update runners
* Restore ubuntu-18.04 back

---------

Signed-off-by: Tao He <sighingnow@gmail.com>
Co-authored-by: Tao He <sighingnow@gmail.com>
2023-01-31 12:53:27 +08:00
Tao He fb41073a90
Reliable macro condition to adapt to different version of gRPC. (#187) 2023-01-31 12:52:19 +08:00
Tao He fe80439c5b Bump up to version 0.2.10 2023-01-10 15:23:07 +08:00
Tao He 10f3435c28
Enable keys() to list elements without fetching values from server (#184)
* Enable keys() to list elements without fetching values from server
* Add concurrency control and cancel to CI workflows

Signed-off-by: Tao He <sighingnow@gmail.com>
2023-01-10 15:00:34 +08:00
Tao He 47a5f5238f Bump up the version to v0.2.9.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-24 14:54:45 +08:00
Tao He 30c880dd05 Document the `client.observe()` API.
Resolves #148.

Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-24 14:53:10 +08:00
Tao He 2f15c45d4e
Protect the timer to avoid "cancel" and "refresh" happens at the same time. (#179)
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-24 10:29:38 +08:00
Tao He c7f17cdf0d Fixes the descirption in the docs, and revisit the order of test cases.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-18 09:21:06 +08:00
Tao He 1e72df7ca3 Add test cases and documentation to show that binary data as key/value works well.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-18 00:52:01 +08:00
Tao He 9f09066b47
Fixes the timeout parameter and some improvements to find non-standard installed deps. (#178)
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-17 13:18:36 +08:00
Tao He 62884c7e38 Avoid conflict with parent projects
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-14 21:13:10 +08:00
CayOest f6af474c5a
Replace CONTROL by vcpkg.json (#176) 2022-10-13 09:14:17 +08:00
Tao He 1fb5abac28
Implements the "etcdctl lease list" functionality (#174)
* Implements the "etcdctl lease list" functionalities.
* LeaseLeases requires etcd >= 3.3

Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-13 00:04:18 +08:00
Tao He 71b5b3a1a3 Update the vcpkg configuration to the latest release.
Resolves #171.

Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-10 23:33:52 +08:00
Tao He bbcab86eab Fixes the format of comment lines
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-10 23:31:39 +08:00
Tao He ab255467d0
Taking error code from grpc. (#172)
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-10-09 10:25:50 +08:00
Tao He 2e38d3c11e Bump up the library version to v0.2.8.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-09-30 14:49:45 +08:00
Tao He c81f3fb211
Watcher: add constructors to accept the wait callback. (#169)
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-09-29 15:18:15 +08:00
Tao He 1108d986a7
Add transaction tests and documentations (#164)
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-09-27 11:31:06 +08:00
Tao He 92efa7c9de Refine the README for openssl, API references, and how to unlock.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-09-24 11:38:17 +08:00
Tao He 39be27e021
Add a sync variant of lock on async client. (#163)
Resolves #139.

Lock is special, as it may cause the thread resources (in the pplx
thread pool) to be exhausted.

Signed-off-by: Tao He <sighingnow@gmail.com>
2022-09-20 21:20:58 +08:00
Tao He ed7ab08ef7 Limit C++ flags to this library itself.
Resolves #159.

Signed-off-by: Tao He <sighingnow@gmail.com>
2022-09-20 20:11:10 +08:00
zhanghan-mq a789dc2f13
build: as submodule, cancel `add_test()` (#160)
if we use `etcd-cpp-apiv3` as submodule, we don't need to test `etcd-cpp-apiv3`.
in `CMakeLists.txt`
```
enable_testing()
add_subdirectory(src)
add_subdirectory(tst)
```
this code force to set `add_test()` and maybe make the project failed to `unit tests`.

Signed-off-by: zhang.han <dalezh@163.com>

Signed-off-by: zhang.han <dalezh@163.com>
Co-authored-by: zhang.han <zhang.han@xsky.com>
2022-09-06 09:55:02 +08:00
wangbinzjut 69a1bf3b30
Update README.md (#155)
Co-authored-by: wangbin13 <wangbin13@corp.netease.com>
2022-08-18 15:39:33 +08:00
Tao He 543a901ee7 Bump up version to v0.2.7.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-08-17 11:37:32 +08:00
Tao He 89ae8a5ee4 Define "NOMINMAX" to ensure `<limits>` works.
See also: https://stackoverflow.com/questions/2561368/illegal-token-on-right-side-of

Resolves #149.

Signed-off-by: Tao He <sighingnow@gmail.com>
2022-08-17 11:34:28 +08:00
Tao He bd42e7129e Use `make -j$(nproc)` rather than plain `make -j`.
Addresses issues in #152.

Signed-off-by: Tao He <sighingnow@gmail.com>
2022-08-17 11:22:17 +08:00
Tao He ca2553e30d
Enable C++11 when testing grpc features. (#146)
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-07-31 16:58:53 +08:00
Tao He efa502831d
Reject lower version of gRPC. (#145)
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-07-31 15:21:38 +08:00
Tao He 2ed6cacce9
Add more variants for etcd::Watcher's constructor. (#144)
* Add more variants for etcd::Watcher's constructor.
* Use ASAN for testing on CI.
* Revert "Use ASAN for testing on CI."

This reverts commit 6ccc2161da.
2022-07-19 20:42:44 +08:00
Tao He 7c9b9e5699 Enable grpc timeout support in keepalive's refresh.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-05-24 00:20:55 +08:00
Tao He 2437a08e72 No timeout for leaserevoke.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-05-23 15:13:47 +08:00
Tao He e5f1167c69 Implements the timeout feature to the etcd client.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-05-22 22:57:38 +08:00
Tao He 8da8946409 Refactor the implementation of sync-client and async-client.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-05-22 21:42:06 +08:00
Tao He f21c45b362 Bump up the version to v0.2.6 for a quickfix of the ctor
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-04-25 23:40:33 +08:00
Tao He 9a5267286b Use a explicit copy constructor as we noticed stack-buffer-overflow inside the copy ctor when ASAN is enabled
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-04-24 10:30:10 +08:00
Tao He ffb489ba4b Bump up the version number to v0.2.5
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-04-21 18:53:39 +08:00
Tao He bd4ec37ff4 Use int64_t for etcd revisions/indexes.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-04-21 18:52:34 +08:00
Tao He 9a2b753e9e Bump up version to v0.2.4.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-04-08 21:26:55 +08:00
Tao He 680e8ad5c6 Replace Tabs in sources and README with spaces.
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-04-08 20:49:56 +08:00
Tao He 56c7189f92 Revisit the watcher's reconnect functionality.
Address #73, #76, #117 and #118.

Signed-off-by: Tao He <sighingnow@gmail.com>
2022-04-08 19:55:31 +08:00
Tao He f70255c8b0 Revert "Add support to re-activate watcher from wait_callback in watcher async wait."
This reverts commit 0467c0eef3.
2022-04-08 19:55:31 +08:00
Tao He 9a2afd4e81 Revert "Update Watcher re-connection sample."
This reverts commit 23394ab9bb.
2022-04-08 19:55:31 +08:00
Tao He d4975f84b3
Return complete meta information of HEAD. (#123)
* Return complete meta information of HEAD.
* Fixes the CI by revisiting the LD_LIBRARY_PATH.

Signed-off-by: Tao He <sighingnow@gmail.com>
2022-04-06 16:01:49 +08:00
Tao He 767f0b1c65
Test against centos latest. (#121)
Signed-off-by: Tao He <sighingnow@gmail.com>
2022-03-30 09:22:18 +08:00
David Pastor 23394ab9bb Update Watcher re-connection sample. 2022-03-29 19:39:04 +08:00
David Pastor 0467c0eef3 Add support to re-activate watcher from wait_callback in watcher async wait.
Returns wait result when cancelling watcher.
2022-03-29 19:39:04 +08:00
Siyuan Zhang 82f632de11
Bump cmake mininum required version to 3.3 (#114)
Signed-off-by: siyuan0322 <siyuan0322@gmail.com>
2022-01-05 19:41:07 +08:00
Tao He 1575c5b43a
Fixes warnings and -Werror errors on Mac with the latest grpc. (#112)
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-12-28 13:28:56 +08:00
Tao He bad5f6ea72 Deb: no debug.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-12-23 19:16:03 +08:00
Tao He 5ad96e21f9
Build the deb package and upload to artifacts. (#111)
* Build the deb package and upload to artifacts.
* Cancel.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-12-23 19:13:02 +08:00
Tao He 99ea53cb79 Bump up version to v0.2.3.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-12-22 14:32:20 +08:00
Tao He 345380a83a
Allow specifying the auth token TTL when auth with password. (#109)
* Allow specifying the auth token TTL when auth with password.
* Fixes.

Resolves #107.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-12-22 14:30:45 +08:00
Tao He 405383c0ba Add a new API to `client.observe()` that accepts a callback.
Resolves #108.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-12-22 10:30:46 +08:00
Tao He 6a0b6696e5 Fixes the hardcode language standard in CMakeLists.txt.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-12-22 10:16:38 +08:00
Tao He 8678cec1f7 Bump up version to v0.2.2.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-11-14 21:23:41 +08:00
Tao He 0d672ffa1c
Accept an `grpc::ChannelArguments` argument in client's constructors. (#104)
* Accept an `grpc::ChannelArguments` argument in client's constructors.

   Resolves #103.

* Backwards compatibility with Ubuntu 18.04.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-11-09 00:31:59 +08:00
Matthew Fioravante bfb56be151
Expose key version in etcd::Value (#97) 2021-10-27 12:49:34 +08:00
Matthew Fioravante 1118222b3d
Make revision numbers 64 bit to match proto spec (#96)
* Make Response::index() 64 bit

* make compact_revision 64 bit

* Make input revision params 64 bit

* make Transaction mod revision 64 bit

* Make Value created and modified index 64 bit

* Fix tests
2021-10-27 10:51:49 +08:00
Tao He d29e05545d Implements the put action.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-10-12 10:17:25 +08:00
Matthew Fioravante e9db91b335
Allow changing Transaction key (#93)
* Allow changing Transaction key
* Fixes the compilation errors.

Signed-off-by: Matthew Fioravante <fmatthew5876@gmail.com>
Co-authored-by: Tao He <sighingnow@gmail.com>
2021-10-08 11:12:35 +08:00
Tao He b99dc2024e
Include compact revision for cancelled watch response. (#91)
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-09-28 11:24:11 +08:00
Tao He a949dec288
Add an optional `target_name_override` to support multiple-endpoints with SSL. (#89)
Resolves #87.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-09-24 02:41:46 +08:00
Tao He 6eed82a766
Fixes memory leak in shutting down gRPC streams. (#88)
* Fixes memory leak in shutting down gRPC streams.

Resolve #86.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-09-23 14:51:29 +08:00
mszy d386bb96b0
Removing memory-leak, fixes #83. (#85)
Co-authored-by: morgan.szygenda <morgan.szygenda@scle.fr>
2021-09-22 10:50:33 +08:00
Tao He cda80854eb
Implements "v3election.proto" APIs. (#84)
Resolves #81.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-09-16 00:46:21 +08:00
Tao He 116b49b784
Lint source code, improve the quality and readability. (#82)
* Lint the source first, prepare for implementing #81.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-09-15 14:59:55 +08:00
Tao He 42c2fdf58c Ugrade vcpkg sources.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-08-24 00:13:10 +08:00
Tao He 1b68f9c79d Fixes for #80 and #79, should set `CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS` instead.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-08-24 00:10:11 +08:00
Tao He 0e4934523e
MSVC: export all global symbols by default. (#80)
Resolves #79.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-08-23 23:33:29 +08:00
Tao He a69638409c The target protobuf::protoc exists since cmake 3.10.
Fixes for cmake version later than v3.10, e.g., cmake 3.9.

See also upstream patch:

    c281acf807

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-08-04 11:27:56 +08:00
Tao He 891dc7bedf Revisit docs about how could we "reconnect" when failure.
See also:

   https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/issues/73#issuecomment-888150328


Signed-off-by: Tao He <sighingnow@gmail.com>
2021-07-28 17:57:33 +08:00
Tao He 3d4fa143e0 Set `etcd-cpp-api_FOUND`, fixes #77.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-07-16 00:56:49 +08:00
Yue 2338a83fd5
Add a brief doc and sample code describing how a watcher can re-connect to etcd server after disconnected. (#76) 2021-07-15 23:55:01 +08:00
Tao He 7559258b87 Update vcpkg configs, fixes #72.
Resolves microsoft/vcpkg#18901 as well.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-07-13 00:53:05 +08:00
Tao He 0dc89e4c1e Add openssl/libssl-dev to requirements.
Resolves #71.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-07-01 18:00:26 +08:00
Yue 6704b9373f
Minor update for vcpkg portfile so that the port can be installed on macOS. And update the portfile to use the latest release v0.2.1. (#69) 2021-06-20 16:10:08 +08:00
Tao He efcecb7731 Add a "head" method on the client the retrieve the latest revision.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-06-16 17:10:32 +08:00
Tao He 68c5626d2c Keepalive: join the thread outside the background job itself.
Fixes #67, when etcd server stops, the keepalive action will crash.


Signed-off-by: Tao He <sighingnow@gmail.com>
2021-06-15 12:03:35 +08:00
Tao He b3a193c75e Fixes several typos in REAEMD.
Fixes #63.

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-06-01 17:27:20 +08:00
Tao He 230ebfd08b
Avoid duplicate const string tags. (#62)
On behalf of Asplund, Rickard, thanks!

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-05-28 23:22:32 +08:00
Tao He 46f36dac6d Fixes a typo in example code in README.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-05-28 14:34:08 +08:00
Tao He 9d794504aa Drop unused log message.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-05-28 10:09:41 +08:00
Tao He 4dda2db5a1 Compatible with older version of gRPC.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-05-27 23:13:04 +08:00
Tao He e80709418b Avoid including protobuf & grpc headers in our interface files.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-05-27 22:10:24 +08:00
Tao He 0b9a4f36ce Upgrade catch.hpp to v2.13.6.
As we have noticed a random "Segmentation fault" error with the following backtrace:

(gdb) bt
#0  _int_free (av=0x7f3db4000020, p=0x7f3db40018a0, have_lock=0) at malloc.c:4199
#1  0x0000557164b22982 in Catch::AssertionInfo::~AssertionInfo (this=0x7f3c32fcc3c8, __in_chrg=<optimized out>) at /home/gsbot/hetao/libvineyard/thirdparty/etcd-cpp-apiv3/catch.hpp:827
#2  0x0000557164b0afd6 in Catch::AssertionResult::~AssertionResult (this=0x7f3c32fcc3c8, __in_chrg=<optimized out>) at /home/gsbot/hetao/libvineyard/thirdparty/etcd-cpp-apiv3/catch.hpp:7275
#3  0x0000557164b117f4 in Catch::AssertionStats::~AssertionStats (this=0x7f3c32fcc3c0, __in_chrg=<optimized out>) at /home/gsbot/hetao/libvineyard/thirdparty/etcd-cpp-apiv3/catch.hpp:10254
#4  0x0000557164b26d8c in Catch::RunContext::assertionEnded (this=0x7ffc893f2e40, result=...) at /home/gsbot/hetao/libvineyard/thirdparty/etcd-cpp-apiv3/catch.hpp:5981
#5  0x0000557164b0f77f in Catch::ResultBuilder::handleResult (this=0x7f3c32fcca00, result=...) at /home/gsbot/hetao/libvineyard/thirdparty/etcd-cpp-apiv3/catch.hpp:8271
#6  0x0000557164b0f6ee in Catch::ResultBuilder::captureExpression (this=0x7f3c32fcca00) at /home/gsbot/hetao/libvineyard/thirdparty/etcd-cpp-apiv3/catch.hpp:8267
#7  0x0000557164b0f2ca in Catch::ResultBuilder::endExpression (this=0x7f3c32fcca00) at /home/gsbot/hetao/libvineyard/thirdparty/etcd-cpp-apiv3/catch.hpp:8229
#8  0x0000557164b4637c in Catch::ExpressionLhs<bool>::endExpression (this=0x7f3c32fcc6f0) at /home/gsbot/hetao/libvineyard/thirdparty/etcd-cpp-apiv3/catch.hpp:1850
#9  0x0000557164b176de in <lambda(const string&, size_t)>::operator()(const std::string &, size_t) const (__closure=0x557164dcb6b0, key="/test/test_key", index=112)
    at /home/gsbot/hetao/libvineyard/thirdparty/etcd-cpp-apiv3/tst/LockTest.cpp:184

Signed-off-by: Tao He <sighingnow@gmail.com>
2021-05-25 13:31:11 +08:00
Tao He eab29edaf0 Fixes unused warnings.
Signed-off-by: Tao He <sighingnow@gmail.com>
2021-05-25 13:29:36 +08:00
92 changed files with 27183 additions and 13062 deletions

15
.clang-format Normal file
View File

@ -0,0 +1,15 @@
BasedOnStyle: Google
DerivePointerAlignment: false
PointerAlignment: Left
Cpp11BracedListStyle: true
IndentCaseLabels: false
AllowShortBlocksOnASingleLine: true
AllowShortLoopsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
Standard: 'Cpp11'
SpaceAfterCStyleCast: true
AlignAfterOpenBracket: Align
SortIncludes: true
IncludeBlocks: Preserve
ForEachMacros:
- BOOST_FOREACH

126
.github/workflows/build-deb-package.yml vendored Normal file
View File

@ -0,0 +1,126 @@
name: Build Deb Package
on: [push, pull_request]
concurrency:
group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }}
cancel-in-progress: true
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04]
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Get time
run: |
date +'%Y-%m' > snapshot.txt
- name: Cache for cccahe
uses: actions/cache@v3
with:
path: /home/runner/.ccache
key: ${{ runner.os }}-deb-ccache-${{ hashFiles('**/snapshot.txt') }}
restore-keys: |
${{ runner.os }}-deb-ccache-
- name: Install dependencies for Linux
if: runner.os == 'Linux'
run: |
sudo apt update -y
sudo apt install -y ca-certificates \
ccache \
cmake \
debhelper \
devscripts \
libboost-all-dev \
libcpprest-dev \
libcurl4-openssl-dev \
libssl-dev \
libz-dev \
lsb-release \
openssl \
screenfetch \
wget
- name: Install grpc for Ubuntu 20.04
if: matrix.os == 'ubuntu-20.04'
run: |
sudo apt install -y libcurl4-openssl-dev \
libprotobuf-dev \
libprotoc-dev \
libgrpc-dev \
libgrpc++-dev \
protobuf-compiler-grpc
- name: Screen fetch
run: |
screenfetch
- name: Setup tmate session
if: false
uses: mxschmitt/action-tmate@v3
- name: Prepare gpg environment
run: |
cat > gpg-script <<EOF
%echo Generating a OpenPGP key
Key-Type: DSA
Key-Length: 1024
Subkey-Type: ELG-E
Subkey-Length: 1024
Name-Real: Tao He
Name-Email: sighingnow@gmail.com
Expire-Date: 0
Passphrase: etcd-passphrase
# Do a commit here, so that we can later print "done" :-)
%commit
%echo done
EOF
gpg --batch --gen-key gpg-script
- name: CMake
run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
mkdir -p build
cd build
cmake .. -DCMAKE_CXX_STANDARD=17 \
-DCMAKE_CXX_STANDARD_REQUIRED=TRUE \
-DBUILD_ETCD_TESTS=ON \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Generate PPA source tarball
if: false
run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
mkdir -p build
cd build
- name: Generate Deb package
run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
mkdir -p build
cd build
cpack -G DEB
- name: Upload deb package
if: always()
uses: actions/upload-artifact@v2
with:
name: deb package
path: build/*.deb
- name: Check ccache
run: |
ccache --show-stats

View File

@ -2,17 +2,52 @@ name: Build and Test
on: [push, pull_request] on: [push, pull_request]
concurrency:
group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }}
cancel-in-progress: true
env:
SEGFAULT_SIGNALS: all
jobs: jobs:
build: build:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false
matrix: matrix:
# disabled: macos-11.0 os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
# why: https://github.com/actions/virtual-environments/issues/2486 etcd: [v3.2.32, v3.3.27, v3.4.27, v3.5.9]
os: [ubuntu-18.04, ubuntu-20.04, macos-10.15] exclude:
etcd: [v3.2.26, v3.3.11, v3.4.13] - os: ubuntu-20.04
etcd: v3.2.26
- os: ubuntu-20.04
etcd: v3.4.13
- os: ubuntu-20.04
etcd: v3.5.7
- os: ubuntu-22.04
etcd: v3.2.26
- os: ubuntu-22.04
etcd: v3.3.11
- os: ubuntu-22.04
etcd: v3.5.7
- os: macos-11
etcd: v3.2.26
- os: macos-11
etcd: v3.3.11
- os: macos-11
etcd: v3.4.13
- os: macos-12
etcd: v3.2.26
- os: macos-12
etcd: v3.3.11
- os: macos-12
etcd: v3.4.13
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
@ -21,7 +56,7 @@ jobs:
date +'%Y-%m' > snapshot.txt date +'%Y-%m' > snapshot.txt
- name: Cache for cccahe - name: Cache for cccahe
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: /home/runner/.ccache path: /home/runner/.ccache
key: ${{ runner.os }}-ccache-${{ hashFiles('**/snapshot.txt') }} key: ${{ runner.os }}-ccache-${{ hashFiles('**/snapshot.txt') }}
@ -47,6 +82,10 @@ jobs:
wget https://github.com/Kitware/CMake/releases/download/v3.19.3/cmake-3.19.3-Linux-x86_64.sh wget https://github.com/Kitware/CMake/releases/download/v3.19.3/cmake-3.19.3-Linux-x86_64.sh
sudo bash cmake-3.19.3-Linux-x86_64.sh --prefix /usr --skip-license sudo bash cmake-3.19.3-Linux-x86_64.sh --prefix /usr --skip-license
# install clang-format
sudo curl -L https://github.com/muttleyxd/clang-tools-static-binaries/releases/download/master-1d7ec53d/clang-format-11_linux-amd64 --output /usr/bin/clang-format
sudo chmod +x /usr/bin/clang-format
- name: Install grpc v1.27.x for Ubuntu 18.04 - name: Install grpc v1.27.x for Ubuntu 18.04
if: matrix.os == 'ubuntu-18.04' if: matrix.os == 'ubuntu-18.04'
run: | run: |
@ -74,8 +113,8 @@ jobs:
make -j`nproc` make -j`nproc`
sudo make install sudo make install
- name: Install grpc for Ubuntu 20.04 - name: Install grpc for Ubuntu 20.04 or later
if: matrix.os == 'ubuntu-20.04' if: ${{ runner.os != 'macOS' && matrix.os != 'ubuntu-18.04' }}
run: | run: |
sudo apt install -y libcurl4-openssl-dev \ sudo apt install -y libcurl4-openssl-dev \
libprotobuf-dev \ libprotobuf-dev \
@ -84,6 +123,10 @@ jobs:
libgrpc++-dev \ libgrpc++-dev \
protobuf-compiler-grpc protobuf-compiler-grpc
# install libsegfault.so
sudo apt-get install libc6 || true
sudo apt-get install glibc-tools || true
- name: Install dependencies for Mac - name: Install dependencies for Mac
if: runner.os == 'macOS' if: runner.os == 'macOS'
run: | run: |
@ -127,26 +170,68 @@ jobs:
make -j`nproc` make -j`nproc`
sudo make install sudo make install
- name: Build - name: Setup tmate session
if: false
uses: mxschmitt/action-tmate@v3
- name: CMake
run: | run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib/x86_64-linux-gnu
mkdir -p build mkdir -p build
cd build cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug \ cmake .. -DCMAKE_CXX_STANDARD=17 \
-DCMAKE_CXX_STANDARD_REQUIRED=TRUE \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_ETCD_TESTS=ON \ -DBUILD_ETCD_TESTS=ON \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Format
if: runner.os == 'Linux'
run: |
cd build
function prepend() { while read line; do echo "${1}${line}"; done; }
make etcd_cpp_apiv3_clformat
GIT_DIFF=$(git diff --ignore-submodules)
if [[ -n $GIT_DIFF ]]; then
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo "| clang-format failures found!"
echo "|"
echo "$GIT_DIFF" | prepend "| "
echo "|"
echo "| Run: "
echo "|"
echo "| make etcd_cpp_apiv3_clformat"
echo "|"
echo "| to fix this error."
echo "|"
echo "| Ensure you are working with clang-format-11, which can be obtained from"
echo "|"
echo "| https://github.com/muttleyxd/clang-tools-static-binaries/releases "
echo "|"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
exit -1
fi
- name: Build
run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib/x86_64-linux-gnu
cd build
make -j`nproc` make -j`nproc`
sudo make install sudo make install
- name: Setup tmate session - name: Setup tmate session
if: false if: false
uses: mxschmitt/action-tmate@v2 uses: mxschmitt/action-tmate@v3
- name: Test - name: Test
run: | run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib/x86_64-linux-gnu
# use etcd v3 api # use etcd v3 api
export ETCDCTL_API="3" export ETCDCTL_API="3"
@ -157,17 +242,99 @@ jobs:
sleep 5 sleep 5
# tests without auth # tests without auth
echo "Run the etcd resolver test ........................."
# there's no ipv6 on github CI runner
# ./build/bin/EtcdResolverTest
echo "Run the etcd sync test ........................."
./build/bin/EtcdSyncTest ./build/bin/EtcdSyncTest
echo "Run the etcd test ........................."
./build/bin/EtcdTest ./build/bin/EtcdTest
./build/bin/LockTest
echo "Run the etcd campaign test ........................."
./build/bin/CampaignTest
echo "Run the etcd memory leak test ........................."
./build/bin/MemLeakTest
echo "Run the etcd watcher test ........................."
./build/bin/WatcherTest ./build/bin/WatcherTest
killall -TERM etcd echo "Run the etcd memory leak in watcher test ........................."
./build/bin/MemLeakWatcherTest
echo "Run the etcd keepalive test ........................."
./build/bin/KeepAliveTest
echo "Run the etcd transaction test ........................."
./build/bin/TransactionTest
echo "Run the etcd election test ........................."
./build/bin/ElectionTest
echo "Run the etcd with fork test ........................."
./build/bin/ForkTest
killall -TERM etcd || true
sleep 5 sleep 5
- name: Lock Test
if: false
run: |
killall -TERM etcd || true
sleep 5
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib/x86_64-linux-gnu
# use etcd v3 api
export ETCDCTL_API="3"
rm -rf default.etcd
/usr/local/bin/etcd &
sleep 5
echo "Run the etcd lock test ........................."
./build/bin/LockTest
killall -TERM etcd || true
sleep 5
- name: Lock Tests with Debug
if: true
uses: sighingnow/action-tmate@master
with:
script-to-run: |
killall -TERM etcd || true
sleep 5
# enable coredump for debugging
ulimit -c unlimited
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib/x86_64-linux-gnu
# use etcd v3 api
export ETCDCTL_API="3"
rm -rf default.etcd
/usr/local/bin/etcd &
sleep 5
echo "Run the etcd lock test ........................."
./build/bin/LockTest
killall -TERM etcd || true
sleep 5
- name: Authentication Test - name: Authentication Test
run: | run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib killall -TERM etcd || true
sleep 5
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib/x86_64-linux-gnu
# use etcd v3 api # use etcd v3 api
export ETCDCTL_API="3" export ETCDCTL_API="3"
@ -183,7 +350,7 @@ jobs:
printf 'root\nroot\n' | /usr/local/bin/etcdctl user add root || true printf 'root\nroot\n' | /usr/local/bin/etcdctl user add root || true
fi fi
# for etcd v3.4 # for etcd v3.4
if [[ "${{ matrix.etcd }}" == v3.4* ]]; if [[ "${{ matrix.etcd }}" == v3.4* ]] || [[ "${{ matrix.etcd }}" == v3.5* ]];
then then
/usr/local/bin/etcdctl user add root --new-user-password="root" || true /usr/local/bin/etcdctl user add root --new-user-password="root" || true
fi fi
@ -198,17 +365,20 @@ jobs:
/usr/local/bin/etcdctl --user="root:root" auth disable || true /usr/local/bin/etcdctl --user="root:root" auth disable || true
fi fi
# for etcd v3.4 # for etcd v3.4
if [[ "${{ matrix.etcd }}" == v3.4* ]]; if [[ "${{ matrix.etcd }}" == v3.4* ]] || [[ "${{ matrix.etcd }}" == v3.5* ]];
then then
/usr/local/bin/etcdctl auth disable --user="root" --password="root" || true /usr/local/bin/etcdctl auth disable --user="root" --password="root" || true
fi fi
killall -TERM etcd killall -TERM etcd || true
sleep 5 sleep 5
- name: Transport Security and Authentication Test - name: Transport Security and Authentication Test
run: | run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib killall -TERM etcd || true
sleep 5
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib/x86_64-linux-gnu
# use etcd v3 api # use etcd v3 api
export ETCDCTL_API="3" export ETCDCTL_API="3"
@ -233,6 +403,26 @@ jobs:
killall -TERM etcd killall -TERM etcd
sleep 5 sleep 5
- name: Etcd Member test
run: |
killall -TERM etcd || true
sleep 5
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib/x86_64-linux-gnu
# use etcd v3 api
export ETCDCTL_API="3"
rm -rf default.etcd
/usr/local/bin/etcd &
sleep 5
./build/bin/EtcdMemberTest
killall -TERM etcd
sleep 5
- name: Check ccache - name: Check ccache
run: | run: |
ccache --show-stats ccache --show-stats

142
.github/workflows/centos-latest.yml vendored Normal file
View File

@ -0,0 +1,142 @@
name: Build & Test on CentOS Latest
on: [push, pull_request]
concurrency:
group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }}
cancel-in-progress: true
jobs:
build:
runs-on: ${{ matrix.os }}
container:
image: centos:latest
strategy:
matrix:
os: [ubuntu-20.04]
etcd: [v3.4.13]
steps:
- name: Install dependencies for Linux
run: |
# switch to centos stream
dnf -y --disablerepo '*' --enablerepo=extras swap centos-linux-repos centos-stream-repos
dnf -y update
# install required dependencies
yum -y group install "Development Tools"
yum -y install boost-devel \
cmake \
git \
openssl-devel \
wget
# install screen fetch
wget -O screenfetch-dev https://git.io/vaHfR
chmod +x screenfetch-dev
mv ./screenfetch-dev /usr/bin/screenfetch
# the checkout action require new version of git
- uses: actions/checkout@v3
with:
submodules: true
- name: Cache for cccahe
uses: actions/cache@v3
with:
path: /home/runner/.ccache
key: ${{ runner.os }}-centos-ccache-${{ hashFiles('/CMakeLists.txt') }}
restore-keys: |
${{ runner.os }}-centos-ccache-
- name: Install grpc v1.27.x for CentOS latest
run: |
# We simply keep the same version with Ubuntu 18.04
#
git clone https://github.com/grpc/grpc.git --depth 1 --branch v1.27.x
cd grpc/
git submodule update --init
mkdir cmake-build
cd cmake-build/
cmake .. -DBUILD_SHARED_LIBS=ON \
-DgRPC_INSTALL=ON \
-DgRPC_BUILD_TESTS=OFF \
-DgRPC_BUILD_CSHARP_EXT=OFF \
-DgRPC_BUILD_GRPC_CSHARP_PLUGIN=OFF \
-DgRPC_BUILD_GRPC_NODE_PLUGIN=OFF \
-DgRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN=OFF \
-DgRPC_BUILD_GRPC_PHP_PLUGIN=OFF \
-DgRPC_BUILD_GRPC_PYTHON_PLUGIN=OFF \
-DgRPC_BUILD_GRPC_RUBY_PLUGIN=OFF \
-DgRPC_BACKWARDS_COMPATIBILITY_MODE=ON \
-DgRPC_ZLIB_PROVIDER=package \
-DgRPC_SSL_PROVIDER=package
make -j`nproc`
make install
- name: Screen fetch
run: |
screenfetch
- name: Install etcd for Linux
run: |
# install etcd
wget https://github.com/etcd-io/etcd/releases/download/${{ matrix.etcd }}/etcd-${{ matrix.etcd }}-linux-amd64.tar.gz
tar zxvf etcd-${{ matrix.etcd }}-linux-amd64.tar.gz
mv etcd-${{ matrix.etcd }}-linux-amd64/etcd /usr/local/bin/
mv etcd-${{ matrix.etcd }}-linux-amd64/etcdctl /usr/local/bin/
- name: Install cpprestsdk
run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib64
mkdir -p build
cd build
git clone https://github.com/microsoft/cpprestsdk.git --depth=1
mkdir -p cpprestsdk/build
cd cpprestsdk/build
cmake .. -DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTS=OFF \
-DBUILD_SAMPLES=OFF \
-DCPPREST_EXCLUDE_WEBSOCKETS=ON
make -j`nproc`
make install
- name: Build
run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib64
mkdir -p build
cd build
cmake .. -DCMAKE_CXX_STANDARD=17 \
-DCMAKE_CXX_STANDARD_REQUIRED=TRUE \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_ETCD_TESTS=ON
make -j`nproc`
make install
- name: Setup tmate session
if: false
uses: mxschmitt/action-tmate@v3
- name: Test
run: |
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib64
# use etcd v3 api
export ETCDCTL_API="3"
rm -rf default.etcd
/usr/local/bin/etcd &
sleep 5
# tests without auth
./build/bin/EtcdSyncTest
./build/bin/EtcdTest
./build/bin/LockTest
./build/bin/MemLeakTest
./build/bin/WatcherTest
./build/bin/ElectionTest
killall -TERM etcd
sleep 5

3
.gitignore vendored
View File

@ -3,3 +3,6 @@ compile_commands.json
proto/**/*.pb.cc proto/**/*.pb.cc
proto/**/*.pb.h proto/**/*.pb.h
default.etcd/ default.etcd/
# vscode-clangd
.cache/

View File

@ -1,15 +1,16 @@
cmake_minimum_required (VERSION 3.1.3 FATAL_ERROR) cmake_minimum_required (VERSION 3.3 FATAL_ERROR)
project (etcd-cpp-api) project (etcd-cpp-api)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(etcd-cpp-api_VERSION_MAJOR 0) set(etcd-cpp-api_VERSION_MAJOR 0)
set(etcd-cpp-api_VERSION_MINOR 2) set(etcd-cpp-api_VERSION_MINOR 15)
set(etcd-cpp-api_VERSION_PATCH 1) set(etcd-cpp-api_VERSION_PATCH 5)
set(etcd-cpp-api_VERSION ${etcd-cpp-api_VERSION_MAJOR}.${etcd-cpp-api_VERSION_MINOR}.${etcd-cpp-api_VERSION_PATCH}) set(etcd-cpp-api_VERSION ${etcd-cpp-api_VERSION_MAJOR}.${etcd-cpp-api_VERSION_MINOR}.${etcd-cpp-api_VERSION_PATCH})
set(CMAKE_PROJECT_HOMEPAGE_URL https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3) set(CMAKE_PROJECT_HOMEPAGE_URL https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3)
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
include(CheckCXXSourceCompiles)
include(CheckLibraryExists) include(CheckLibraryExists)
include(GNUInstallDirs) include(GNUInstallDirs)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@ -25,22 +26,91 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
) )
endif() endif()
option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(BUILD_WITH_NO_EXCEPTIONS "Build etcd-cpp-apiv3 with disabling exception handling, i.e., -fno-exceptions" OFF)
option(BUILD_ETCD_TESTS "Build test cases" OFF) option(BUILD_SHARED_LIBS "Build etcd-cpp-apiv3 shared libraries" ON)
option(BUILD_ETCD_CORE_ONLY "Build etcd-cpp-apiv3 core library (the synchronous runtime) only" OFF)
option(BUILD_ETCD_TESTS "Build etcd-cpp-apiv3 test cases" OFF)
option(CMAKE_POSITION_INDEPENDENT_CODE "Build etcd-cpp-apiv3 with -fPIC" ON)
option(ETCD_W_STRICT "Build etcd-cpp-apiv3 with -Werror" ON)
option(ETCD_CMAKE_CXX_STANDARD "Build etcd-cpp-apiv3 with specified C++ standard, e.g., 11, 14, 17, 20" "")
if(NOT "${ETCD_CMAKE_CXX_STANDARD}")
if(NOT "${CMAKE_CXX_STANDARD}")
set(ETCD_CMAKE_CXX_STANDARD 11)
else()
set(ETCD_CMAKE_CXX_STANDARD ${CMAKE_CXX_STANDARD})
endif()
endif()
message(STATUS "Building etcd-cpp-apiv3 with C++${ETCD_CMAKE_CXX_STANDARD}")
# reference: https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling#always-full-rpath # reference: https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling#always-full-rpath
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib:${CMAKE_INSTALL_PREFIX}/lib64") set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib:${CMAKE_INSTALL_PREFIX}/lib64:${CMAKE_INSTALL_PREFIX}/lib/x86_64-linux-gnu")
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
find_package(Boost REQUIRED COMPONENTS system thread random) if(MSVC)
if (APPLE) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_GNUtoMS ON)
endif()
if(NOT (CMAKE_CXX_COMPILER_LAUNCHER MATCHES "ccache") AND NOT (CMAKE_C_COMPILER_LAUNCHER MATCHES "ccache"))
find_program(ccache_EXECUTABLE ccache)
if(ccache_EXECUTABLE)
set(CMAKE_C_COMPILER_LAUNCHER ${ccache_EXECUTABLE})
set(CMAKE_CXX_COMPILER_LAUNCHER ${ccache_EXECUTABLE})
if (NOT TARGET ccache-stats)
add_custom_target(ccache-stats
COMMAND ${ccache_EXECUTABLE} --show-stats
)
endif()
else()
if (NOT TARGET ccache-stats)
add_custom_target(ccache-stats
COMMAND echo "ccache not found."
)
endif()
endif(ccache_EXECUTABLE)
endif()
macro(use_cxx target)
if(CMAKE_VERSION VERSION_LESS "3.1")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${target} PRIVATE "-std=gnu++${ETCD_CMAKE_CXX_STANDARD}")
else()
target_compile_options(${target} PRIVATE "-std=c++${ETCD_CMAKE_CXX_STANDARD}")
endif()
elseif(CMAKE_VERSION VERSION_LESS "3.8")
set_target_properties(${target} PROPERTIES
CXX_STANDARD ${ETCD_CMAKE_CXX_STANDARD}
CXX_STANDARD_REQUIRED ON
)
else()
target_compile_features(${target} PUBLIC cxx_std_${ETCD_CMAKE_CXX_STANDARD})
endif()
endmacro(use_cxx)
macro(set_exceptions target)
if(BUILD_WITH_NO_EXCEPTIONS)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${target} PRIVATE "-fno-exceptions")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(${target} PRIVATE "-fno-exceptions")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
target_compile_options(${target} PRIVATE "-fno-exceptions")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(${target} PRIVATE "/EHs-c-")
endif()
target_compile_definitions(${target} PUBLIC -D_ETCD_NO_EXCEPTIONS)
endif()
endmacro(set_exceptions)
if(APPLE)
# If we're on OS X check for Homebrew's copy of OpenSSL instead of Apple's # If we're on OS X check for Homebrew's copy of OpenSSL instead of Apple's
if (NOT OpenSSL_DIR) if(NOT OpenSSL_DIR)
find_program(HOMEBREW brew) find_program(HOMEBREW brew)
if (HOMEBREW STREQUAL "HOMEBREW-NOTFOUND") if(HOMEBREW STREQUAL "HOMEBREW-NOTFOUND")
message(WARNING "Homebrew not found: not using Homebrew's OpenSSL") message(WARNING "Homebrew not found: not using Homebrew's OpenSSL")
if (NOT OPENSSL_ROOT_DIR) if(NOT OPENSSL_ROOT_DIR)
message(WARNING "Use -DOPENSSL_ROOT_DIR for non-Apple OpenSSL") message(WARNING "Use -DOPENSSL_ROOT_DIR for non-Apple OpenSSL")
endif() endif()
else() else()
@ -52,60 +122,138 @@ if (APPLE)
endif() endif()
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
find_package(Protobuf REQUIRED) find_package(Protobuf CONFIG QUIET)
find_package(cpprestsdk QUIET) if (NOT Protobuf_FOUND)
if(cpprestsdk_FOUND) find_package(Protobuf REQUIRED)
set(CPPREST_INCLUDE_DIR) endif()
set(CPPREST_LIB cpprestsdk::cpprest) if(Protobuf_PROTOC_EXECUTABLE)
else() if(NOT TARGET protobuf::protoc)
find_library(CPPREST_LIB NAMES cpprest) add_executable(protobuf::protoc IMPORTED)
find_path(CPPREST_INCLUDE_DIR NAMES cpprest/http_client.h) if(EXISTS "${Protobuf_PROTOC_EXECUTABLE}")
set_target_properties(protobuf::protoc PROPERTIES
IMPORTED_LOCATION "${Protobuf_PROTOC_EXECUTABLE}")
endif()
endif()
endif()
# When cross compiling we look for the native protoc compiler
# overwrite protobuf::protoc with the proper protoc
if(CMAKE_CROSSCOMPILING)
find_program(Protobuf_PROTOC_EXECUTABLE REQUIRED NAMES protoc)
if(NOT TARGET protobuf::protoc)
add_executable(protobuf::protoc IMPORTED)
endif()
set_target_properties(protobuf::protoc PROPERTIES
IMPORTED_LOCATION "${Protobuf_PROTOC_EXECUTABLE}")
endif() endif()
find_package(gRPC QUIET) find_package(gRPC QUIET)
if(gRPC_FOUND) if(gRPC_FOUND AND TARGET gRPC::grpc)
# When cross compiling we look for the native grpc_cpp_plugin
if(CMAKE_CROSSCOMPILING)
find_program(GRPC_CPP_PLUGIN REQUIRED NAMES grpc_cpp_plugin)
if(NOT TARGET gRPC::grpc_cpp_plugin)
add_executable(gRPC::grpc_cpp_plugin IMPORTED)
endif()
set_target_properties(gRPC::grpc_cpp_plugin PROPERTIES
IMPORTED_LOCATION "${GRPC_CPP_PLUGIN}")
elseif(TARGET gRPC::grpc_cpp_plugin)
get_target_property(GRPC_CPP_PLUGIN gRPC::grpc_cpp_plugin LOCATION)
else()
message(FATAL_ERROR "Found gRPC but no gRPC CPP plugin defined")
endif()
set(GRPC_LIBRARIES gRPC::gpr gRPC::grpc gRPC::grpc++) set(GRPC_LIBRARIES gRPC::gpr gRPC::grpc gRPC::grpc++)
get_target_property(GRPC_CPP_PLUGIN gRPC::grpc_cpp_plugin LOCATION) get_target_property(GRPC_INCLUDE_DIR gRPC::grpc INTERFACE_INCLUDE_DIRECTORIES)
else() else()
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindGRPC.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindGRPC.cmake)
set(GRPC_LIBRARIES ${GPR_LIBRARY} ${GRPC_LIBRARY} ${GRPC_GRPC++_LIBRARY}) set(GRPC_LIBRARIES ${GPR_LIBRARY} ${GRPC_LIBRARY} ${GRPC_GRPC++_LIBRARY})
endif() endif()
message(STATUS "Found GRPC: ${GRPC_LIBRARIES}, ${GRPC_CPP_PLUGIN} (found version: \"${gRPC_VERSION}\")")
# avoid use the apt-get installed libgrpc-dev (version v1.13) on Ubuntu 18.04
if(gRPC_FOUND AND gRPC_VERSION VERSION_LESS "1.14")
message(FATAL_ERROR "gRPC '${gRPC_VERSION}' is not supported, please install a newer gRPC library "
"by following the example below"
"\n"
" https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/blob/master/.github/workflows/build-test.yml#L50-L75")
endif()
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenerateProtobufGRPC.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenerateProtobufGRPC.cmake)
if(gRPC_VERSION VERSION_LESS "1.21" OR gRPC_VERSION VERSION_GREATER "1.31")
add_definitions(-DWITH_GRPC_CHANNEL_CLASS)
endif()
if(gRPC_VERSION VERSION_LESS "1.17")
add_definitions(-DWITH_GRPC_CREATE_CHANNEL_INTERNAL_UNIQUE_POINTER)
endif()
# will set `PROTOBUF_GENERATES`, indicates all generated .cc files, and a target `protobuf_generates`. # will set `PROTOBUF_GENERATES`, indicates all generated .cc files, and a target `protobuf_generates`.
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenerateProtobuf.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenerateProtobuf.cmake)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/proto) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/proto)
include_directories(SYSTEM ${Boost_INCLUDE_DIR} # test cases requires the async runtime
${CPPREST_INCLUDE_DIR} if(BUILD_ETCD_TESTS)
message(STATUS "Building etcd-cpp-apiv3 with tests requires the async runtime, "
"setting BUILD_ETCD_CORE_ONLY to ON ...")
set(BUILD_ETCD_CORE_ONLY OFF)
endif()
if (NOT BUILD_ETCD_CORE_ONLY)
find_package(cpprestsdk QUIET)
if(cpprestsdk_FOUND)
set(CPPREST_INCLUDE_DIR)
set(CPPREST_LIB cpprestsdk::cpprest)
else()
find_library(CPPREST_LIB NAMES cpprest)
find_path(CPPREST_INCLUDE_DIR NAMES cpprest/http_client.h)
endif()
else()
set(CPPREST_INCLUDE_DIR)
set(CPPREST_LIB)
endif()
include_directories(SYSTEM ${CPPREST_INCLUDE_DIR}
${PROTOBUF_INCLUDE_DIRS} ${PROTOBUF_INCLUDE_DIRS}
${GRPC_INCLUDE_DIR}
${OPENSSL_INCLUDE_DIR}) ${OPENSSL_INCLUDE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR})
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Werror -Wno-string-compare -std=c++11") if(ETCD_W_STRICT)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Wno-string-compare")
endif()
check_cxx_compiler_flag(-Wno-c++17-extensions W_NO_CPP17_EXTENSIONS)
check_cxx_compiler_flag(-Wno-extra-semi W_NO_EXTRA_SEMI)
if(W_NO_CPP17_EXTENSIONS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++17-extensions")
endif()
if(W_NO_EXTRA_SEMI)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-extra-semi")
endif() endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
add_subdirectory(src) add_subdirectory(src)
if(BUILD_ETCD_TESTS)
enable_testing()
add_subdirectory(tst)
endif()
if (BUILD_ETCD_TESTS) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/KeepAlive.hpp
enable_testing() ${CMAKE_CURRENT_SOURCE_DIR}/etcd/SyncClient.hpp
add_subdirectory(tst) ${CMAKE_CURRENT_SOURCE_DIR}/etcd/Response.hpp
endif () ${CMAKE_CURRENT_SOURCE_DIR}/etcd/Value.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/Watcher.hpp
install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/Client.hpp DESTINATION include/etcd)
${CMAKE_CURRENT_SOURCE_DIR}/etcd/KeepAlive.hpp if(NOT BUILD_ETCD_CORE_ONLY)
${CMAKE_CURRENT_SOURCE_DIR}/etcd/SyncClient.hpp install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/Client.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/Response.hpp DESTINATION include/etcd)
${CMAKE_CURRENT_SOURCE_DIR}/etcd/Value.hpp endif()
${CMAKE_CURRENT_SOURCE_DIR}/etcd/Watcher.hpp install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/action_constants.hpp
${CMAKE_CURRENT_BINARY_DIR}/proto/gen/proto/kv.pb.h ${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/Transaction.hpp
DESTINATION include/etcd) ${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/Member.hpp
install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/Transaction.hpp DESTINATION include/etcd/v3)
${CMAKE_CURRENT_BINARY_DIR}/proto/gen/proto/txn.pb.h
DESTINATION include/etcd/v3)
configure_file(etcd-cpp-api-config.in.cmake configure_file(etcd-cpp-api-config.in.cmake
"${PROJECT_BINARY_DIR}/etcd-cpp-api-config.cmake" @ONLY "${PROJECT_BINARY_DIR}/etcd-cpp-api-config.cmake" @ONLY
@ -123,6 +271,17 @@ install(EXPORT etcd-targets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/etcd-cpp-api DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/etcd-cpp-api
) )
file(GLOB_RECURSE FILES_NEED_FORMAT
"etcd/*.hpp"
"src/*.cpp"
"tst/*.cpp"
)
add_custom_target(etcd_cpp_apiv3_clformat
COMMAND clang-format --style=file -i ${FILES_NEED_FORMAT}
COMMENT "Running clang-format, using clang-format-11 from https://github.com/muttleyxd/clang-tools-static-binaries/releases"
VERBATIM)
# deb/rpc packaging for Linux # deb/rpc packaging for Linux
set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME}) set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME})
@ -168,7 +327,7 @@ set(CPACK_DEBIAN_PACKAGE_DEPENDS "libcpprest-dev,
libgrpc++-dev, libgrpc++-dev,
libssl-dev") libssl-dev")
set(CPACK_DEBIAN_PACKAGE_UPSTREAM_COPYRIGHT_YEAR 2016-2021) set(CPACK_DEBIAN_PACKAGE_UPSTREAM_COPYRIGHT_YEAR 2016-2023)
set(CPACK_DEBIAN_PACKAGE_LICENSE bsd) set(CPACK_DEBIAN_PACKAGE_LICENSE bsd)
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Tao He <sighingnow@gmail.com>") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Tao He <sighingnow@gmail.com>")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/")
@ -177,16 +336,22 @@ set(CPACK_DEBIAN_PACKAGE_INSTALL "/usr/lib/lib*.so*"
"/usr/lib/cmake/etcd-cpp-api/*.cmake" "/usr/lib/cmake/etcd-cpp-api/*.cmake"
"/usr/include/etcd/*.h" "/usr/include/etcd/*.h"
"/usr/include/etcd/*.hpp" "/usr/include/etcd/*.hpp"
"/usr/include/etcd/v3/*.h" "/usr/include/etcd/v3/action_constants.hpp"
"/usr/include/etcd/v3/*.hpp" "/usr/include/etcd/v3/Transaction.hpp"
"/usr/include/etcd/v3/Member.hpp"
) )
set(CPACK_DEBIAN_PACKAGE_BUILD_NUMBER_PREFIX "") set(CPACK_DEBIAN_PACKAGE_BUILD_NUMBER_PREFIX "")
set(CPACK_DEBIAN_PACKAGE_BUILD_NUMBER 0) set(CPACK_DEBIAN_PACKAGE_BUILD_NUMBER 0)
set(CPACK_DEBIAN_PACKAGE_DISTRIBUTION "focal") set(CPACK_DEBIAN_PACKAGE_DISTRIBUTION "focal")
set(DPUT_HOST "ppa:graphscope/etcd-cpp-api") set(DPUT_HOST "ppa:etcd-cpp-apiv3/etcd-cpp-api")
set(DPUT_SNAPSHOT_HOST "ppa:graphscope/etcd-cpp-api") set(DPUT_SNAPSHOT_HOST "ppa:etcd-cpp-apiv3/etcd-cpp-api")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") find_program(DEBUILD_EXECUTABLE debuild)
include(UploadPPA) find_program(DPUT_EXECUTABLE dput)
if(DEBUILD_EXECUTABLE AND DPUT_EXECUTABLE)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(UploadPPA)
endif()

707
README.md
View File

@ -9,13 +9,16 @@ i.e., `ETCDCTL_API=3`.
### Supported OS environments ### Supported OS environments
+ **Linux** + **Linux**
- Ubuntu 18.04, requires upgrade grpc libraries (has been tested with 1.27.x). - Ubuntu 18.04, requires upgrade gRPC libraries (tested with 1.27.x).
- Ubuntu 20.04 - Ubuntu 20.04
- CentOS 8 (tested with 1.27.x)
+ **MacOS** + **MacOS**
- MacOS 10.15 - MacOS 10.15
- MacOS 11.0 - MacOS 11.0
+ **Windows** + **Windows**
- Windows 10, with [vcpkg](https://github.com/microsoft/vcpkg) - Windows 10, with [vcpkg](https://github.com/microsoft/vcpkg/tree/master/ports/etcd-cpp-apiv3)
### Supported etcd versions: ### Supported etcd versions:
@ -25,18 +28,17 @@ i.e., `ETCDCTL_API=3`.
## Requirements ## Requirements
1. boost 1. boost and openssl (**Note that boost is only required if you need the asynchronous runtime**)
+ On Ubuntu, above requirement could be installed as: + On Ubuntu, above requirement could be installed as:
apt-get install libboost-all-dev apt-get install libboost-all-dev libssl-dev
+ On MacOS, above requirement could be installed as: + On MacOS, above requirement could be installed as:
brew install boost brew install boost openssl
2. protobuf 2. protobuf & gRPC
3. gRPC
+ On Ubuntu, above requirements related to protobuf and gRPC can be installed as: + On Ubuntu, above requirements related to protobuf and gRPC can be installed as:
@ -44,21 +46,31 @@ i.e., `ETCDCTL_API=3`.
libgrpc++-dev \ libgrpc++-dev \
libprotobuf-dev \ libprotobuf-dev \
protobuf-compiler-grpc protobuf-compiler-grpc
+ On MacOS, above requirements related to protobuf and gRPC can be installed as:
+ On MacOS, above requirements related to protobuf and gRPC can be installed as:
brew install grpc protobuf brew install grpc protobuf
+ When building grpc from source code (e.g., on [Ubuntu 18.04](https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/blob/master/.github/workflows/build-test.yml#L73)
and on [CentOS](https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/blob/master/.github/workflows/centos-latest.yml#L44-L67)),
if the system-installed openssl is preferred, you need to add `-DgRPC_SSL_PROVIDER=package`
when building gRPC with CMake.
4. [cpprestsdk](https://github.com/microsoft/cpprestsdk), the latest version of master branch 3. [cpprestsdk](https://github.com/microsoft/cpprestsdk), the latest version of master branch
on github should work, you can build and install this dependency using cmake with: on github should work, you can build and install this dependency using cmake with:
git clone https://github.com/microsoft/cpprestsdk.git git clone https://github.com/microsoft/cpprestsdk.git
cd cpprestsdk cd cpprestsdk
mkdir build && cd build mkdir build && cd build
cmake .. -DCPPREST_EXCLUDE_WEBSOCKETS=ON cmake .. -DCPPREST_EXCLUDE_WEBSOCKETS=ON
make -j && make install 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](https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3#etcd-operations)
in README, and see the detail C++ interfaces in [Client.hpp](https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/blob/master/etcd/Client.hpp)
and [SyncClient.hpp](https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/blob/master/etcd/SyncClient.hpp).
## Build and install ## Build and install
@ -69,47 +81,103 @@ dependencies have been successfully installed:
cd etcd-cpp-apiv3 cd etcd-cpp-apiv3
mkdir build && cd build mkdir build && cd build
cmake .. cmake ..
make -j && make install make -j$(nproc) && make install
## Using this package in your CMake project
To use this package in your CMake project, you can either
- install, then find the library using `find_package()`:
```cmake
find_package(etcd-cpp-apiv3 REQUIRED)
target_link_libraries(your_target PRIVATE etcd-cpp-api)
```
- or, add this repository as a subdirectory in your project, and link the library directly:
```cmake
add_subdirectory(thirdparty/etcd-cpp-apiv3)
target_link_libraries(your_target PRIVATE etcd-cpp-api)
```
- or, use [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html):
```cmake
include(FetchContent)
FetchContent_Declare(
etcd-cpp-apiv3
https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3.git
)
FetchContent_MakeAvailable(etcd-cpp-apiv3)
target_link_libraries(your_target PRIVATE etcd-cpp-api)
```
## Compatible etcd version ## Compatible etcd version
The _etcd-cpp-apiv3_ should work well with etcd > 3.0. Feel free to issue an issue to us on 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. 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](https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/issues/100) and
[issue#207](https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/issues/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 ## Usage
```c++ ```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(); etcd::Response response = etcd.get("/test/key1").get();
std::cout << response.value().as_string(); std::cout << response.value().as_string();
``` ```
Methods of the etcd client object are sending the corresponding gRPC requests and are returning Methods of the etcd client object are sending the corresponding gRPC requests and are returning
immediatelly with a ```pplx::task``` object. The task object is responsible for handling the 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 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 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 current etcd operation is executing in the background or you can wait for the response with the
```wait()``` or ```get()``` methods if a synchron behaviour is enough for your needs. These methods `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 are blocking until the HTTP response arrives or some error situation happens. `get()` method
also returns the ```etcd::Response``` object. also returns the `etcd::Response` object.
```c++ ```c++
etcd::Client etcd("http://127.0.0.1:4001"); etcd::Client etcd("http://127.0.0.1:2379");
pplx::task<etcd::Response> response_task = etcd.get("/test/key1"); pplx::task<etcd::Response> response_task = etcd.get("/test/key1");
// ... do something else // ... do something else
etcd::Response response = response_task.get(); etcd::Response response = response_task.get();
std::cout << response.value().as_string(); std::cout << response.value().as_string();
``` ```
The pplx library allows to do even more. You can attach continuation ojects to the task if you do 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 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 funcion object parameter to 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 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 callback should be either a `etcd::Response` or a `pplx::task<etcd:Response>`. You should
probably use a C++ lambda funcion here as a callback. probably use a C++ lambda function here as a callback.
```c++ ```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) etcd.get("/test/key1").then([](etcd::Response response)
{ {
std::cout << response.value().as_string(); std::cout << response.value().as_string();
@ -118,22 +186,22 @@ probably use a C++ lambda funcion here as a callback.
// ... your code can continue here without any delay // ... your code can continue here without any delay
``` ```
Your lambda function should have a parameter of type ```etcd::Response``` or 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``` `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 exeptions so this is the way 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 how you can catch the errors generated by the REST interface. The `get()` call will not block in
this case since the respose has been already arrived (we are inside the callback). this case since the response has been already arrived (we are inside the callback).
```c++ ```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<etcd::Response> response_task) etcd.get("/test/key1").then([](pplx::task<etcd::Response> response_task)
{ {
try try
{ {
etcd::Response response = response.task.get(); // can throw etcd::Response response = response_task.get(); // can throw
std::cout << response.value().as_string(); std::cout << response.value().as_string();
} }
catch (std::ecxeption const & ex) catch (std::exception const & ex)
{ {
std::cerr << ex.what(); std::cerr << ex.what();
} }
@ -142,8 +210,6 @@ this case since the respose has been already arrived (we are inside the callback
// ... your code can continue here without any delay // ... your code can continue here without any delay
``` ```
## etcd operations
### Multiple endpoints ### Multiple endpoints
Connecting to multiple endpoints is supported: Connecting to multiple endpoints is supported:
@ -156,6 +222,14 @@ Connecting to multiple endpoints is supported:
etcd::Client etcd("http://a.com:2379;http://b.com:2379;http://c.com:2379"); etcd::Client etcd("http://a.com:2379;http://b.com:2379;http://c.com:2379");
``` ```
### IPv6
Connecting to IPv6 endpoints is supported:
```c++
etcd::Client etcd("http://::1:2379");
```
Behind the screen, gRPC's load balancer is used and the round-robin strategy will Behind the screen, gRPC's load balancer is used and the round-robin strategy will
be used by default. be used by default.
@ -171,13 +245,20 @@ the authentication properly.
etcd::Client etcd("http://127.0.0.1:2379", "root", "root"); etcd::Client etcd("http://127.0.0.1:2379", "root", "root");
``` ```
Or the etcd client can be constructed explictly: Or the etcd client can be constructed explicitly:
```c++ ```c++
etcd::Client *etcd = etcd::Client::WithUser( etcd::Client *etcd = etcd::Client::WithUser(
"http://127.0.0.1:2379", "root", "root"); "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:
```c++
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). 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 First you need to set the `ETCDCTL_API=3`, then
@ -203,8 +284,8 @@ printf 'root\nroot\n' | /usr/local/bin/etcdctl user add root
Etcd [transport security](https://etcd.io/docs/v3.4.0/op-guide/security/) and certificate based Etcd [transport security](https://etcd.io/docs/v3.4.0/op-guide/security/) and certificate based
authentication have been supported as well. The `Client::Client` could accept arguments `ca` , authentication have been supported as well. The `Client::Client` could accept arguments `ca` ,
`cert` and `key` for CA cert, cert and private key files for the SSL/TLS transport and authentication. `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 `key` could be empty strings or omitted if you just need 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` secure transport and don't enable certificate-based client authentication (using the `--client-cert-auth`
arguments when launching etcd server). arguments when launching etcd server).
@ -214,7 +295,7 @@ arguments when launching etcd server).
"round_robin"); "round_robin");
``` ```
Or the etcd client can be constructed explictly: Or the etcd client can be constructed explicitly:
```c++ ```c++
etcd::Client *etcd = etcd::Client::WithSSL( etcd::Client *etcd = etcd::Client::WithSSL(
@ -236,26 +317,148 @@ transport security using openssl.
We also provide a tool [`setup-ca.sh`](./security-config/setup-ca.sh) as a helper for development and testing. We also provide a tool [`setup-ca.sh`](./security-config/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`,
```cpp
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](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).
### 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,
```cpp
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.,
```cpp
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,
```cpp
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
```cpp
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 ### Reading a value
You can read a value with the ```get``` method of the clinent instance. The only parameter is the 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 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 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 `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. `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 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 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 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 attemp to read a non-existing key then the 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. 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 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 key. You can also get the index number of the creation and the last modification of this key with
the ```created_index()``` and the ```modofied_index()``` methods. the `created_index()` and the `modified_index()` methods.
```c++ ```c++
etcd::Client etcd("http://127.0.0.1:4001"); etcd::Client etcd("http://127.0.0.1:2379");
pplx::task<etcd::Response> response_task = etcd.get("/test/key1"); pplx::task<etcd::Response> response_task = etcd.get("/test/key1");
try try
@ -266,23 +469,33 @@ the ```created_index()``` and the ```modofied_index()``` methods.
else else
std::cout << "operation failed, details: " << response.error_message(); std::cout << "operation failed, details: " << response.error_message();
} }
catch (std::ecxeption const & ex) catch (std::exception const & ex)
{ {
std::cerr << "communication problem, details: " << ex.what(); std::cerr << "communication problem, details: " << ex.what();
} }
``` ```
### Modifying a value ### Put a value
Setting the value of a key can be done with the ```set()``` method of the client. You simply pass You can put a key-value pair to etcd with the the `put()` method of the client instance. The only
the key and the value as string parameters and you are done. The newly set value object can be asked parameter is the key and value to be put
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.
```c++ ```c++
etcd::Client etcd("http://127.0.0.1:4001"); 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.
```c++
etcd::Client etcd("http://127.0.0.1:2379");
pplx::task<etcd::Response> response_task = etcd.set("/test/key1", "42"); pplx::task<etcd::Response> response_task = etcd.set("/test/key1", "42");
try try
@ -294,7 +507,7 @@ what was the previous value that this operation was overwritten. You can do that
else else
std::cout << "operation failed, details: " << response.error_message(); std::cout << "operation failed, details: " << response.error_message();
} }
catch (std::ecxeption const & ex) catch (std::exception const & ex)
{ {
std::cerr << "communication problem, details: " << ex.what(); std::cerr << "communication problem, details: " << ex.what();
} }
@ -304,28 +517,29 @@ The set method creates a new leaf node if it weren't exists already or modifies
There are a couple of other modification methods that are executing the write operation only upon There are a couple of other modification methods that are executing the write operation only upon
some specific conditions. some specific conditions.
* ```add(key, value)``` creates a new value if it's key does not exists and returns a "Key * `add(key, value)` creates a new value if it's key does not exists and returns a "Key
already exists" error otherwise (error code 105) already exists" error otherwise (error code `ERROR_KEY_ALREADY_EXISTS`)
* ```modify(key, value)``` modifies an already existing value or returns a "Key not found" error * `modify(key, value)` modifies an already existing value or returns a "etcd-cpp-apiv3: key not found" error
otherwise (error code 100) otherwise (error code `KEY_NOT_FOUND`)
* ```modify_if(key, value, old_value)``` modifies an already existing value but only if the previous * `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 value equals with old_value. If the values does not match returns with "Compare failed" error
(code 101) (code `ERROR_COMPARE_FAILED`)
* ```modify_if(key, value, old_index)``` modifies an already existing value but only if the index of * `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 the previous value equals with old_index. If the indices does not match returns with "Compare
failed" error (code 101) failed" error (code `ERROR_COMPARE_FAILED`)
### Deleting a value ### Deleting a value
Values can be deleted with the ```rm``` method passing the key to be deleted as a parameter. The key 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. should point to an existing value. There are conditional variations for deletion too.
* ```rm_if(key, value, old_value)``` deletes an already existing value but only if the previous * `rm(std::string const& key)` unconditionally deletes the given key
value equals with old_value. If the values does not match returns with "Compare failed" error * `rm_if(key, old_value)` deletes an already existing value but only if the previous
(code 101) value equals with old_value. If the values does not match returns with "Compare failed" error
* ```rm_if(key, value, old_index)``` deletes an already existing value but only if the index of (code `ERROR_COMPARE_FAILED`)
the previous value equals with old_index. If the indices does not match returns with "Compare * `rm_if(key, old_index)` deletes an already existing value but only if the index of
failed" error (code 101) 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 ### Handling directory nodes
@ -339,78 +553,103 @@ keys defined by the prefix. mkdir method is removed since etcdv3 treats everythi
2. Listing a directory: 2. Listing a directory:
Listing directory in etcd3 cpp client will return all keys that matched the given prefix Listing directory in etcd3 cpp client will return all keys that matched the given prefix
recursively. recursively.
```c++ ```c++
etcd.set("/test/key1", "value1").wait(); etcd.set("/test/key1", "value1").wait();
etcd.set("/test/key2", "value2").wait(); etcd.set("/test/key2", "value2").wait();
etcd.set("/test/key3", "value3").wait(); etcd.set("/test/key3", "value3").wait();
etcd.set("/test/subdir/foo", "foo").wait(); etcd.set("/test/subdir/foo", "foo").wait();
etcd::Response resp = etcd.ls("/test/new_dir").get(); etcd::Response resp = etcd.ls("/test").get();
``` ```
```resp.key()``` will have the following values: `resp.key()` will have the following values:
``` ```
/test/key1 /test/key1s
/test/key2 /test/key2
/test/key3 /test/key3
/test/subdir/foo /test/subdir/foo
``` ```
Note: Regarding the returned keys when listing a directory: 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 + In etcdv3 cpp client, resp.key(0) will return "/test/new_dir/key1" since everything is
treated as keys in etcdv3. treated as keys in etcdv3.
+ While in etcdv2 cpp client it will return "key1" and "/test/new_dir" directory should + While in etcdv2 cpp client it will return "key1" and "/test/new_dir" directory should
be created first before you can set "key1". be created first before you can set "key1".
When you list a directory the response object's ```keys()``` and ```values()``` methods gives 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 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] == returns with the i-th element of the values vector, so `response.values()[i] == response.value(i)`.
response.value(i)```.
```c++ ```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(); etcd::Response resp = etcd.ls("/test/new_dir").get();
for (int i = 0; i < resp.keys().size(); ++i) for (int i = 0; i < resp.keys().size(); ++i)
{ {
std::cout << resp.keys(i); std::cout << resp.keys(i);
std::cout << " = " << resp.value(i).as_string() << std::endl; std::cout << " = " << resp.value(i).as_string() << std::endl;
} }
``` ```
etcd-cpp-apiv3 supports lists keys only without fetching values from etcd server:
```c++
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: 3. Removing directory:
If you want the delete recursively then you have to pass a second ```true``` parameter 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 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 prefix will be deleted. All deleted keys will be placed in `response.values()` and
```response.keys()```. This parameter defaults to ```false```. `response.keys()`. This parameter defaults to `false`.
```c++
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()`,
```c++ ```c++
etcd::Client etcd("http://127.0.0.1:4001"); std::string key = "key-foo\0bar";
etcd.set("/test/key1", "foo"); std::string value = "value-foo\0bar";
etcd.set("/test/key2", "bar"); etcd.put(key, value).wait();
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.
### Lock ### Lock
Etcd lock has been supported as follows: Etcd lock has been supported as follows:
```c++ ```c++
etcd::Client etcd("http://127.0.0.1:4001"); etcd::Client etcd("http://127.0.0.1:2379");
etcd.lock("/test/lock"); etcd.lock("/test/lock");
``` ```
@ -420,28 +659,41 @@ the lock is unlocked.
Users can also feed their own lease directory for lock: Users can also feed their own lease directory for lock:
```c++ ```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); 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()`:
```c++
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 changes
Watching for a change is possible with the ```watch()``` operation of the client. The watch method 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 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 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 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 `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 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 `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. 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 As mentioned in the section "handling directory nodes", directory nodes are not supported anymore
in etcdv3. in etcdv3.
However it is still possible to watch a whole "directory subtree", or more specifically a set of 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``` 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 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. 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 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. exception and you have to restart the watch operation.
@ -468,17 +720,17 @@ void watch_for_changes()
} }
``` ```
At first glance it seems that ```watch_for_changes()``` calls itself on every value change but in At first glance it seems that `watch_for_changes()` calls itself on every value change but in
fact it just sends the asynchron request, sets up a callback for the response and then returns. The 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 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. case a small lambda function actually) will call `watch_for_changes()` again from there.
#### Watcher Class #### Watcher Class
Users can watch a key indefinitely or until user cancels the watch. This can be done by 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 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 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. either by user implicitly calling `Cancel()` or when watcher class is destroyed.
```c++ ```c++
etcd::Watcher watcher("http://127.0.0.1:2379", "/test", printResponse); etcd::Watcher watcher("http://127.0.0.1:2379", "/test", printResponse);
@ -487,18 +739,77 @@ either by user implicitly calling ```Cancel()``` or when watcher class is destro
watcher.Cancel(); watcher.Cancel();
etcd.set("/test/key", "43"); /* print response will NOT be called, etcd.set("/test/key", "43"); /* print response will NOT be called,
since watch is already cancelled */ 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.
```c++
// 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
```c++
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](./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 ### Requesting for lease
Users can request for lease which is governed by a time-to-live(TTL) value given by the user. 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()```, 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 `set()`, `modify()` and `modify_if()`. Also the ttl will that was granted by etcd
server will be indicated in ```ttl()```. server will be indicated in `ttl()`.
```c++ ```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::Response resp = etcd.leasegrant(60).get();
etcd.set("/test/key2", "bar", resp.value().lease()); etcd.set("/test/key2", "bar", resp.value().lease());
std::cout << "ttl" << resp.value().ttl(); std::cout << "ttl" << resp.value().ttl();
@ -526,13 +837,13 @@ The remaining time-to-live of a lease can be inspected by
#### Keep alive #### Keep alive
Keep alive for leases is implemented using a seperate class `KeepAlive`, which can be used as: Keep alive for leases is implemented using a separate class `KeepAlive`, which can be used as:
```c++ ```c++
etcd::KeepAlive keepalive(etcd, ttl, lease_id); etcd::KeepAlive keepalive(etcd, ttl, lease_id);
``` ```
It will perform a periodly keep-alive action before it is cancelled explicitly, or destructed implicitly. 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` `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 could accept a handler of type `std::function<std::exception_ptr>` and the handler will be invoked
@ -559,7 +870,115 @@ is constructed.
Without handler, the internal state can be checked via `KeepAlive::Check()` and it will rethrow 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. the async exception when there are errors during keeping the lease alive.
### TODO 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](https://etcd.io/docs/v3.4/learning/api/#transaction) is supported via the
`etcdv3::Transaction` interfaces. A set of convenient APIs are use to add operations to a transaction, e.g.,
```cpp
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.,
```cpp
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
```cpp
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](./tst/TransactionTest.cpp),
for full list of the transaction operation APIs, see [./etcd/v3/Transaction.hpp](./etcd/v3/Transaction.hpp).
### Election API
Etcd v3's [election APIs](https://github.com/etcd-io/etcd/blob/main/server/etcdserver/api/v3election/v3electionpb/v3election.proto)
are supported via the following interfaces,
```c++
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.
```c++
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](./etcd/Client.hpp) and [tst/ElectionTest.cpp](./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) 1. Cancellation of asynchronous calls(except for watch)

24957
catch.hpp

File diff suppressed because it is too large Load Diff

View File

@ -22,51 +22,89 @@ mark_as_advanced(GRPC_INCLUDE_DIR)
# Find gGPR library # Find gGPR library
find_library(GPR_LIBRARY NAMES gpr) find_library(GPR_LIBRARY NAMES gpr)
mark_as_advanced(GRPC_GPR_LIBRARY) mark_as_advanced(GRPC_GPR_LIBRARY)
add_library(gRPC::gpr UNKNOWN IMPORTED) if(NOT TARGET gRPC::gpr)
set_target_properties(gRPC::gpr PROPERTIES add_library(gRPC::gpr UNKNOWN IMPORTED)
INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR} set_target_properties(gRPC::gpr PROPERTIES
INTERFACE_LINK_LIBRARIES "-lpthread;-ldl" INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
IMPORTED_LOCATION ${GPR_LIBRARY} INTERFACE_LINK_LIBRARIES "-lpthread;-ldl"
) IMPORTED_LOCATION ${GPR_LIBRARY}
)
endif()
# Find gRPC library # Find gRPC library
find_library(GRPC_LIBRARY NAMES grpc) find_library(GRPC_LIBRARY NAMES grpc)
mark_as_advanced(GRPC_LIBRARY) mark_as_advanced(GRPC_LIBRARY)
add_library(gRPC::grpc UNKNOWN IMPORTED) if(NOT TARGET gRPC::grpc)
set_target_properties(gRPC::grpc PROPERTIES add_library(gRPC::grpc UNKNOWN IMPORTED)
INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR} set_target_properties(gRPC::grpc PROPERTIES
INTERFACE_LINK_LIBRARIES gRPC::gpr INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
IMPORTED_LOCATION ${GRPC_LIBRARY} INTERFACE_LINK_LIBRARIES gRPC::gpr
) IMPORTED_LOCATION ${GRPC_LIBRARY}
)
endif()
# Find gRPC C++ library # Find gRPC C++ library
find_library(GRPC_GRPC++_LIBRARY NAMES grpc++) find_library(GRPC_GRPC++_LIBRARY NAMES grpc++)
mark_as_advanced(GRPC_GRPC++_LIBRARY) mark_as_advanced(GRPC_GRPC++_LIBRARY)
add_library(gRPC::grpc++ UNKNOWN IMPORTED) if(NOT TARGET gRPC::grpc++)
set_target_properties(gRPC::grpc++ PROPERTIES add_library(gRPC::grpc++ UNKNOWN IMPORTED)
INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR} set_target_properties(gRPC::grpc++ PROPERTIES
INTERFACE_LINK_LIBRARIES gRPC::grpc INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
IMPORTED_LOCATION ${GRPC_GRPC++_LIBRARY} INTERFACE_LINK_LIBRARIES gRPC::grpc
) IMPORTED_LOCATION ${GRPC_GRPC++_LIBRARY}
)
endif()
# Find gRPC C++ reflection library # Find gRPC C++ reflection library
find_library(GRPC_GRPC++_REFLECTION_LIBRARY NAMES grpc++_reflection) find_library(GRPC_GRPC++_REFLECTION_LIBRARY NAMES grpc++_reflection)
mark_as_advanced(GRPC_GRPC++_REFLECTION_LIBRARY) mark_as_advanced(GRPC_GRPC++_REFLECTION_LIBRARY)
add_library(gRPC::grpc++_reflection UNKNOWN IMPORTED) if(NOT TARGET gRPC::grpc++_reflection)
set_target_properties(gRPC::grpc++_reflection PROPERTIES add_library(gRPC::grpc++_reflection UNKNOWN IMPORTED)
INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR} set_target_properties(gRPC::grpc++_reflection PROPERTIES
INTERFACE_LINK_LIBRARIES gRPC::grpc++ INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
IMPORTED_LOCATION ${GRPC_GRPC++_REFLECTION_LIBRARY} INTERFACE_LINK_LIBRARIES gRPC::grpc++
) IMPORTED_LOCATION ${GRPC_GRPC++_REFLECTION_LIBRARY}
)
endif()
# Find gRPC CPP generator # Find gRPC CPP generator
find_program(GRPC_CPP_PLUGIN NAMES grpc_cpp_plugin) find_program(GRPC_CPP_PLUGIN NAMES grpc_cpp_plugin)
mark_as_advanced(GRPC_CPP_PLUGIN) mark_as_advanced(GRPC_CPP_PLUGIN)
add_executable(gRPC::grpc_cpp_plugin IMPORTED) if(NOT TARGET gRPC::grpc_cpp_plugin)
set_target_properties(gRPC::grpc_cpp_plugin PROPERTIES add_executable(gRPC::grpc_cpp_plugin IMPORTED)
IMPORTED_LOCATION ${GRPC_CPP_PLUGIN} set_target_properties(gRPC::grpc_cpp_plugin PROPERTIES
) IMPORTED_LOCATION ${GRPC_CPP_PLUGIN}
)
endif()
file(
WRITE "${CMAKE_BINARY_DIR}/get_gRPC_version.cc"
[====[
#include <grpc++/grpc++.h>
#include <iostream>
int main() {
std::cout << grpc::Version(); // no newline to simplify CMake module
return 0;
}
]====])
try_run(
_gRPC_GET_VERSION_STATUS
_gRPC_GET_VERSION_COMPILE_STATUS
"${CMAKE_BINARY_DIR}"
"${CMAKE_BINARY_DIR}/get_gRPC_version.cc"
CMAKE_FLAGS
-DCMAKE_CXX_STANDARD=11
LINK_LIBRARIES
gRPC::grpc++
gRPC::grpc
COMPILE_OUTPUT_VARIABLE _gRPC_GET_VERSION_COMPILE_OUTPUT
RUN_OUTPUT_VARIABLE gRPC_VERSION)
file(REMOVE "${CMAKE_BINARY_DIR}/get_gRPC_version.cc")
include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(gRPC DEFAULT_MSG FIND_PACKAGE_HANDLE_STANDARD_ARGS(gRPC
GPR_LIBRARY GRPC_LIBRARY GRPC_INCLUDE_DIR GRPC_GRPC++_REFLECTION_LIBRARY GRPC_CPP_PLUGIN) FOUND_VAR gRPC_FOUND
REQUIRED_VARS GRPC_LIBRARY GPR_LIBRARY GRPC_INCLUDE_DIR GRPC_GRPC++_REFLECTION_LIBRARY GRPC_CPP_PLUGIN
VERSION_VAR gRPC_VERSION)

View File

@ -14,6 +14,7 @@ if(NOT gRPC_FOUND)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
find_dependency(GRPC) find_dependency(GRPC)
endif() endif()
find_dependency(cpprestsdk) find_dependency(cpprestsdk)
if(cpprestsdk_FOUND) if(cpprestsdk_FOUND)
set(CPPREST_LIB cpprestsdk::cpprest) set(CPPREST_LIB cpprestsdk::cpprest)
@ -22,12 +23,17 @@ endif()
set(ETCD_CPP_HOME "${CMAKE_CURRENT_LIST_DIR}/../../..") set(ETCD_CPP_HOME "${CMAKE_CURRENT_LIST_DIR}/../../..")
include("${CMAKE_CURRENT_LIST_DIR}/etcd-targets.cmake") include("${CMAKE_CURRENT_LIST_DIR}/etcd-targets.cmake")
set(etcd-cpp-api_FOUND TRUE)
set(ETCD_CPP_LIBRARIES etcd-cpp-api) set(ETCD_CPP_LIBRARIES etcd-cpp-api)
set(ETCD_CPP_CORE_LIBRARIES etcd-cpp-api-core)
set(ETCD_CPP_INCLUDE_DIR "${ETCD_CPP_HOME}/include") set(ETCD_CPP_INCLUDE_DIR "${ETCD_CPP_HOME}/include")
set(ETCD_CPP_INCLUDE_DIRS "${ETCD_CPP_INCLUDE_DIR}") set(ETCD_CPP_INCLUDE_DIRS "${ETCD_CPP_INCLUDE_DIR}")
include(FindPackageMessage) include(FindPackageMessage)
find_package_message(etcd find_package_message(etcd
"Found etcd: ${CMAKE_CURRENT_LIST_FILE} (found version \"@etcd-cpp-api_VERSION@\")" "Found etcd: ${CMAKE_CURRENT_LIST_FILE} (found version \"@etcd-cpp-api_VERSION@\")"
"etcd-cpp-apiv3 version: @etcd-cpp-api_VERSION@\netcd-cpp-apiv3 libraries: ${ETCD_CPP_LIBRARIES}, include directories: ${ETCD_CPP_INCLUDE_DIRS}" "etcd-cpp-apiv3 version: @etcd-cpp-api_VERSION@\n"
"etcd-cpp-apiv3 libraries: ${ETCD_CPP_LIBRARIES}\n"
"etcd-cpp-apiv3 core libraries: ${ETCD_CPP_CORE_LIBRARIES}\n"
"include directories: ${ETCD_CPP_INCLUDE_DIRS}"
) )

File diff suppressed because it is too large Load Diff

View File

@ -2,96 +2,122 @@
#define __ETCD_KEEPALIVE_HPP__ #define __ETCD_KEEPALIVE_HPP__
#include <atomic> #include <atomic>
#include <chrono>
#include <condition_variable>
#include <exception> #include <exception>
#include <functional> #include <functional>
#include <mutex>
#include <string> #include <string>
#include <thread> #include <thread>
#include "etcd/Client.hpp"
#include "etcd/Response.hpp" #include "etcd/Response.hpp"
#include "etcd/SyncClient.hpp"
#include <boost/config.hpp> namespace etcd {
#if BOOST_VERSION >= 106600 // forward declaration to avoid header/library dependency
#include <boost/asio/io_context.hpp> class Client;
#else
#include <boost/asio/io_service.hpp> /**
#endif * If ID is set to 0, the library will choose an ID, and can be accessed from
#include <boost/asio/steady_timer.hpp> * ".Lease()".
*/
class KeepAlive {
public:
KeepAlive(Client const& client, int ttl, int64_t lease_id = 0);
KeepAlive(SyncClient const& client, int ttl, int64_t lease_id = 0);
KeepAlive(std::string const& address, int ttl, int64_t lease_id = 0);
KeepAlive(std::string const& address, std::string const& username,
std::string const& password, int ttl, int64_t lease_id = 0,
int const auth_token_ttl = 300);
KeepAlive(Client const& client,
std::function<void(std::exception_ptr)> const& handler, int ttl,
int64_t lease_id = 0);
KeepAlive(SyncClient const& client,
std::function<void(std::exception_ptr)> const& handler, int ttl,
int64_t lease_id = 0);
KeepAlive(std::string const& address,
std::function<void(std::exception_ptr)> const& handler, int ttl,
int64_t lease_id = 0);
KeepAlive(std::string const& address, std::string const& username,
std::string const& password,
std::function<void(std::exception_ptr)> const& handler, int ttl,
int64_t lease_id = 0, int const auth_token_ttl = 300);
KeepAlive(std::string const& address, std::string const& ca,
std::string const& cert, std::string const& privkey,
std::function<void(std::exception_ptr)> const& handler, int ttl,
int64_t lease_id = 0, std::string const& target_name_override = "");
KeepAlive(KeepAlive const&) = delete;
KeepAlive(KeepAlive&&) = delete;
int64_t Lease() const { return lease_id; }
namespace etcd
{
/** /**
* If ID is set to 0, the library will choose an ID, and can be accessed from ".Lease()". * Stop the keep alive action.
*/ */
class KeepAlive void Cancel();
{
public:
KeepAlive(Client const &client,
int ttl, int64_t lease_id=0);
KeepAlive(std::string const & address,
int ttl, int64_t lease_id=0);
KeepAlive(std::string const & address,
std::string const & username, std::string const & password,
int ttl, int64_t lease_id=0);
KeepAlive(Client const &client, /**
std::function<void (std::exception_ptr)> const &handler, * Check if the keep alive is still valid (invalid when there's an async
int ttl, int64_t lease_id=0); * exception).
KeepAlive(std::string const & address, *
std::function<void (std::exception_ptr)> const &handler, * Nothing will happen if valid and an exception will be rethrowed if invalid.
int ttl, int64_t lease_id=0); */
KeepAlive(std::string const & address, void Check();
std::string const & username, std::string const & password,
std::function<void (std::exception_ptr)> const &handler,
int ttl, int64_t lease_id=0);
KeepAlive(KeepAlive const &) = delete; /**
KeepAlive(KeepAlive &&) = delete; * Set a timeout value for grpc operations.
*/
template <typename Rep = std::micro>
void set_grpc_timeout(std::chrono::duration<Rep> const& timeout) {
this->grpc_timeout =
std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
}
int64_t Lease() const { return lease_id; } /**
* Get the current timeout value for grpc operations.
*/
std::chrono::microseconds get_grpc_timeout() const {
return this->grpc_timeout;
}
/** ~KeepAlive();
* Stop the keep alive action.
*/
void Cancel();
/** protected:
* Check if the keep alive is still valid (invalid when there's an async exception). // automatically refresh loop, returns the error message if failed
* std::string refresh();
* Nothing will happen if valid and an exception will be rethrowed if invalid. // refresh once immediately, returns the error message if failed
*/ std::string refresh_once();
void Check();
~KeepAlive(); struct EtcdServerStubs;
struct EtcdServerStubsDeleter {
protected: void operator()(EtcdServerStubs* stubs);
void refresh();
struct EtcdServerStubs;
struct EtcdServerStubsDeleter {
void operator()(EtcdServerStubs *stubs);
};
std::unique_ptr<EtcdServerStubs, EtcdServerStubsDeleter> stubs;
private:
// error handling
std::exception_ptr eptr_;
std::function<void (std::exception_ptr)> handler_;
// Don't use `pplx::task` to avoid sharing thread pool with other actions on the client
// to avoid any potential blocking, which may block the keepalive loop and evict the lease.
std::thread task_;
int ttl;
int64_t lease_id;
std::atomic_bool continue_next;
#if BOOST_VERSION >= 106600
boost::asio::io_context context;
#else
boost::asio::io_service context;
#endif
std::unique_ptr<boost::asio::steady_timer> keepalive_timer_;
}; };
} std::unique_ptr<EtcdServerStubs, EtcdServerStubsDeleter> stubs;
private:
// error handling
std::exception_ptr eptr_;
std::function<void(std::exception_ptr)> handler_;
// Don't use `pplx::task` to avoid sharing thread pool with other actions on
// the client to avoid any potential blocking, which may block the keepalive
// loop and evict the lease.
std::thread refresh_task_;
int ttl;
int64_t lease_id;
// protect the initializing status of `timer`.
std::mutex mutex_for_refresh_;
std::condition_variable cv_for_refresh_;
std::atomic_bool continue_next;
// grpc timeout in `refresh()`
mutable std::chrono::microseconds grpc_timeout =
std::chrono::microseconds::zero();
};
} // namespace etcd
#endif #endif

View File

@ -1,170 +1,262 @@
#ifndef __ETCD_RESPONSE_HPP__ #ifndef __ETCD_RESPONSE_HPP__
#define __ETCD_RESPONSE_HPP__ #define __ETCD_RESPONSE_HPP__
#include <chrono>
#include <functional>
#include <iostream> #include <iostream>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "pplx/pplxtasks.h"
#include "etcd/Value.hpp" #include "etcd/Value.hpp"
#include "kv.pb.h" #include "etcd/v3/Member.hpp"
namespace etcdv3 { namespace etcdv3 {
class AsyncWatchAction; class AsyncWatchAction;
class AsyncLeaseKeepAliveAction; class AsyncLeaseKeepAliveAction;
class V3Response; class AsyncObserveAction;
} class V3Response;
} // namespace etcdv3
namespace etcd namespace etcd {
{ typedef std::vector<std::string> Keys;
typedef std::vector<std::string> Keys;
namespace detail {
// Compute the duration between given start time point and now
inline const std::chrono::microseconds duration_till_now(
std::chrono::high_resolution_clock::time_point const& start_timepoint) {
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now() - start_timepoint);
}
} // namespace detail
// forward declaration
class KeepAlive;
class Watcher;
/**
* The Response object received for the requests of etcd::Client
*/
class Response {
public:
template <typename T>
static etcd::Response create(std::unique_ptr<T> call) {
call->waitForResponse();
auto v3resp = call->ParseResponse();
return etcd::Response(v3resp,
detail::duration_till_now(call->startTimepoint()));
}
template <typename T>
static etcd::Response create(std::shared_ptr<T> call) {
call->waitForResponse();
auto v3resp = call->ParseResponse();
return etcd::Response(v3resp,
detail::duration_till_now(call->startTimepoint()));
}
template <typename T>
static etcd::Response create(std::unique_ptr<T> call,
std::function<void(Response)> callback) {
call->waitForResponse(callback);
auto v3resp = call->ParseResponse();
return etcd::Response(v3resp,
detail::duration_till_now(call->startTimepoint()));
}
template <typename T>
static etcd::Response create(std::function<std::unique_ptr<T>()> fn) {
auto call = fn();
call->waitForResponse();
auto v3resp = call->ParseResponse();
return etcd::Response(v3resp,
detail::duration_till_now(call->startTimepoint()));
}
template <typename T>
static etcd::Response create(std::function<std::shared_ptr<T>()> fn) {
auto call = fn();
call->waitForResponse();
auto v3resp = call->ParseResponse();
return etcd::Response(v3resp,
detail::duration_till_now(call->startTimepoint()));
}
Response();
Response(const Response&);
/** /**
* The Reponse object received for the requests of etcd::Client * Returns the error code received from the etcd server. In case of success
* the error code is 0.
*/ */
class Response int error_code() const;
{
public:
template <typename T> /**
static pplx::task<etcd::Response> create(std::shared_ptr<T> call) * Returns the string representation of the error code
{ */
return pplx::task<etcd::Response>([call]() std::string const& error_message() const;
{
call->waitForResponse();
auto v3resp = call->ParseResponse();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>( /**
std::chrono::high_resolution_clock::now() - call->startTimepoint()); * Returns true if this is a successful response
return etcd::Response(v3resp, duration); */
}); bool is_ok() const;
}
template <typename T> /**
static pplx::task<etcd::Response> create(std::function<std::shared_ptr<T>()> callfn) * Returns true if the error is a network unavailable error.
{ */
return pplx::task<etcd::Response>([callfn]() bool is_network_unavailable() const;
{
auto call = callfn();
call->waitForResponse(); /**
auto v3resp = call->ParseResponse(); * Check whether the response contains a grpc TIMEOUT error.
*/
bool is_grpc_timeout() const;
auto duration = std::chrono::duration_cast<std::chrono::microseconds>( /**
std::chrono::high_resolution_clock::now() - call->startTimepoint()); * Returns the action type of the operation that this response belongs to.
return etcd::Response(v3resp, duration); */
}); std::string const& action() const;
}
template <typename T> /**
static etcd::Response create_sync(std::shared_ptr<T> call) * Returns the current index value of etcd
{ */
call->waitForResponse(); int64_t index() const;
auto v3resp = call->ParseResponse();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>( /**
std::chrono::high_resolution_clock::now() - call->startTimepoint()); * Returns the value object of the response to a get/set/modify operation.
return etcd::Response(v3resp, duration); */
} Value const& value() const;
Response(); /**
* Returns the previous value object of the response to a set/modify/rm
* operation.
*/
Value const& prev_value() const;
/** /**
* Returns true if this is a successful response * Returns the index-th value of the response to an 'ls' operation. Equivalent
*/ * to values()[index]
bool is_ok() const; */
Value const& value(int index) const;
/** /**
* Returns true if the error is a network unavailable error. * Returns the vector of values in a directory in response to an 'ls'
*/ * operation.
bool is_network_unavailable() const; */
Values const& values() const;
/** /**
* Returns the error code received from the etcd server. In case of success the error code is 0. * Returns the vector of keys in a directory in response to an 'ls' operation.
*/ */
int error_code() const; Keys const& keys() const;
/** /**
* Returns the string representation of the error code * Returns the index-th key in a directory listing. Same as keys()[index]
*/ */
std::string const & error_message() const; std::string const& key(int index) const;
/** /**
* Returns the action type of the operation that this response belongs to. * Returns the compact_revision if the response is a watch-cancelled revision.
*/ * `-1` means uninitialized (the response is not watch-cancelled)
std::string const & action() const; */
int64_t compact_revision() const;
/** /**
* Returns the current index value of etcd * Returns the watcher id for client.watch() requests. `-1` means
*/ * uninitialized (the response is not for watch).
int index() const; */
int64_t watch_id() const;
/** /**
* Returns the value object of the response to a get/set/modify operation. * Returns the lock key.
*/ */
Value const & value() const; std::string const& lock_key() const;
/** /**
* Returns the previous value object of the response to a set/modify/rm operation. * Return the "name" in response.
*/ */
Value const & prev_value() const; std::string const& name() const;
/** /**
* Returns the index-th value of the response to an 'ls' operation. Equivalent to values()[index] * Returns the watched events.
*/ */
Value const & value(int index) const; std::vector<Event> const& events() const;
/** /**
* Returns the vector of values in a directory in response to an 'ls' operation. * Returns the duration of request execution in microseconds.
*/ */
Values const & values() const; std::chrono::microseconds const& duration() const;
/** /**
* Returns the vector of keys in a directory in response to an 'ls' operation. * Returns the current cluster id.
*/ */
Keys const & keys() const; uint64_t cluster_id() const;
/** /**
* Returns the index-th key in a directory listing. Same as keys()[index] * Returns the current member id.
*/ */
std::string const & key(int index) const; uint64_t member_id() const;
/** /**
* Returns the lock key. * Returns ther current raft term.
*/ */
std::string const & lock_key() const; uint64_t raft_term() const;
/** /**
* Returns the watched events. * Returns ther current raft term.
*/ */
std::vector<mvccpb::Event> const & events() const; std::vector<int64_t> const& leases() const;
/** /**
* Returns the duration of request execution in microseconds. * Returns the member list.
*/ */
std::chrono::microseconds const & duration() const; std::vector<etcdv3::Member> const& members() const;
protected: protected:
Response(const etcdv3::V3Response& response, std::chrono::microseconds const& duration); Response(const etcdv3::V3Response& response,
Response(int error_code, char const * error_message); std::chrono::microseconds const& duration);
Response(int error_code, std::string const& error_message);
Response(int error_code, char const* error_message);
int _error_code; int _error_code;
std::string _error_message; std::string _error_message;
int _index; int64_t _index;
std::string _action; std::string _action;
Value _value; Value _value;
Value _prev_value; Value _prev_value;
Values _values; Values _values;
Keys _keys; Keys _keys;
std::string _lock_key; // for lock int64_t _compact_revision = -1; // for watch
std::vector<mvccpb::Event> _events; // for watch int64_t _watch_id = -1; // for watch
std::chrono::microseconds _duration; // execute duration (in microseconds), during the action created and response parsed std::string _lock_key; // for lock
friend class SyncClient; std::string _name; // for campaign (in v3election)
friend class etcdv3::AsyncWatchAction; std::vector<Event> _events; // for watch
friend class etcdv3::AsyncLeaseKeepAliveAction; // execute duration (in microseconds), during the action created and response
friend class Client; // parsed
}; std::chrono::microseconds _duration;
}
// cluster metadata
uint64_t _cluster_id;
uint64_t _member_id;
uint64_t _raft_term;
// for lease list
std::vector<int64_t> _leases;
// for member list
std::vector<etcdv3::Member> _members;
friend class Client;
friend class SyncClient;
friend class KeepAlive;
friend class Watcher;
friend class etcdv3::AsyncWatchAction;
friend class etcdv3::AsyncLeaseKeepAliveAction;
friend class etcdv3::AsyncObserveAction;
};
} // namespace etcd
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@ -5,67 +5,131 @@
#include <vector> #include <vector>
namespace etcdv3 { namespace etcdv3 {
class KeyValue; class KeyValue;
} }
namespace etcd namespace mvccpb {
{ class KeyValue;
class Event;
} // namespace mvccpb
namespace electionpb {
class LeaderKey;
}
namespace etcd {
class Value;
class Event;
class Response;
class Client;
class SyncClient;
/**
* Represents a value object received from the etcd server
*/
class Value {
public:
/** /**
* Represents a value object received from the etcd server * Returns true if this value represents a directory on the server. If true
* the as_string() method is meaningless.
*/ */
class Value bool is_dir() const;
{
public:
/**
* Returns true if this value represents a directory on the server. If true the as_string()
* method is meaningless.
*/
bool is_dir() const;
/** /**
* Returns the key of this value as an "absolute path". * Returns the key of this value as an "absolute path".
*/ */
std::string const & key() const; std::string const& key() const;
/** /**
* Returns the string representation of the value * Returns the string representation of the value
*/ */
std::string const & as_string() const; std::string const& as_string() const;
/** /**
* Returns the creation index of this value. * Returns the creation index of this value.
*/ */
int created_index() const; int64_t created_index() const;
/** /**
* Returns the last modification's index of this value. * Returns the last modification's index of this value.
*/ */
int modified_index() const; int64_t modified_index() const;
/** /**
* Returns the ttl of this value or 0 if ttl is not set * Returns the version of this value.
*/ */
int ttl() const; int64_t version() const;
int64_t lease() const;
protected: /**
friend class Response; * Returns the ttl of this value or 0 if ttl is not set
friend class BaseResponse; //deliberately done since Value class will be removed during full V3 */
friend class DeleteRpcResponse; int ttl() const;
friend class AsyncDeleteResponse;
Value(); int64_t lease() const;
Value(etcdv3::KeyValue const & kvs);
std::string _key; protected:
bool dir; friend class Client;
std::string value; friend class SyncClient;
int created; friend class Response;
int modified; friend class BaseResponse; // deliberately done since Value class will be
int _ttl; // removed during full V3
int64_t leaseId; friend class DeleteRpcResponse;
friend class AsyncDeleteResponse;
friend class Event;
Value();
Value(etcdv3::KeyValue const& kvs);
Value(mvccpb::KeyValue const& kvs);
std::string _key;
bool dir;
std::string value;
int64_t created;
int64_t modified;
int64_t _version;
int _ttl;
int64_t leaseId;
};
using Values = std::vector<Value>;
std::ostream& operator<<(std::ostream& os, const Value& value);
class Event {
public:
enum class EventType {
PUT,
DELETE_,
INVALID,
}; };
typedef std::vector<Value> Values; enum EventType event_type() const;
}
bool has_kv() const;
bool has_prev_kv() const;
const Value& kv() const;
const Value& prev_kv() const;
protected:
friend class Response;
Event(mvccpb::Event const& event);
private:
enum EventType event_type_;
Value _kv, _prev_kv;
bool _has_kv, _has_prev_kv;
};
using Events = std::vector<Event>;
std::ostream& operator<<(std::ostream& os, const Event::EventType& value);
std::ostream& operator<<(std::ostream& os, const Event& event);
} // namespace etcd
#endif #endif

View File

@ -6,101 +6,204 @@
#include <string> #include <string>
#include <thread> #include <thread>
#include "etcd/Client.hpp"
#include "etcd/Response.hpp" #include "etcd/Response.hpp"
namespace etcd namespace etcd {
{ // forward declaration to avoid header/library dependency
class Watcher class Client;
{
public:
Watcher(Client const &client, std::string const & key,
std::function<void(Response)> callback, bool recursive=false);
Watcher(Client const &client, std::string const & key,
std::string const &range_end,
std::function<void(Response)> callback);
Watcher(Client const &client, std::string const & key, int fromIndex,
std::function<void(Response)> callback, bool recursive=false);
Watcher(Client const &client, std::string const & key,
std::string const &range_end, int fromIndex,
std::function<void(Response)> callback);
Watcher(std::string const & address, std::string const & key,
std::function<void(Response)> callback, bool recursive=false);
Watcher(std::string const & address, std::string const & key,
std::string const &range_end,
std::function<void(Response)> callback);
Watcher(std::string const & address, std::string const & key, int fromIndex,
std::function<void(Response)> callback, bool recursive=false);
Watcher(std::string const & address, std::string const & key,
std::string const &range_end, int fromIndex,
std::function<void(Response)> callback);
Watcher(std::string const & address,
std::string const & username, std::string const & password,
std::string const & key,
std::function<void(Response)> callback, bool recursive=false);
Watcher(std::string const & address,
std::string const & username, std::string const & password,
std::string const & key, std::string const &range_end,
std::function<void(Response)> callback);
Watcher(std::string const & address,
std::string const & username, std::string const & password,
std::string const & key, int fromIndex,
std::function<void(Response)> callback, bool recursive=false);
Watcher(std::string const & address,
std::string const & username, std::string const & password,
std::string const & key, std::string const &range_end, int fromIndex,
std::function<void(Response)> callback);
Watcher(Watcher const &) = delete; class Watcher {
Watcher(Watcher &&) = delete; public:
Watcher(Client const& client, std::string const& key,
std::function<void(Response)> callback, bool recursive = false);
Watcher(SyncClient const& client, std::string const& key,
std::function<void(Response)> callback, bool recursive = false);
Watcher(Client const& client, std::string const& key,
std::string const& range_end, std::function<void(Response)> callback);
Watcher(SyncClient const& client, std::string const& key,
std::string const& range_end, std::function<void(Response)> callback);
Watcher(Client const& client, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, bool recursive = false);
Watcher(SyncClient const& client, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, bool recursive = false);
Watcher(Client const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback);
Watcher(SyncClient const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback);
Watcher(std::string const& address, std::string const& key,
std::function<void(Response)> callback, bool recursive = false);
Watcher(std::string const& address, std::string const& key,
std::string const& range_end, std::function<void(Response)> callback);
Watcher(std::string const& address, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, bool recursive = false);
Watcher(std::string const& address, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback);
Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
std::function<void(Response)> callback, bool recursive = false,
int const auth_token_ttl = 300);
Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
std::string const& range_end, std::function<void(Response)> callback,
int const auth_token_ttl = 300);
Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
int64_t fromIndex, std::function<void(Response)> callback,
bool recursive = false, int const auth_token_ttl = 300);
Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback,
int const auth_token_ttl = 300);
Watcher(std::string const& address, std::string const& ca,
std::string const& cert, std::string const& privkey,
std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, bool recursive = false,
std::string const& target_name_override = "");
Watcher(std::string const& address, std::string const& ca,
std::string const& cert, std::string const& privkey,
std::string const& key, std::string const& range_end,
int64_t fromIndex, std::function<void(Response)> callback,
std::string const& target_name_override = "");
/** Watcher(Client const& client, std::string const& key,
* Wait util the task has been stopped, actively or passively, e.g., the watcher std::function<void(Response)> callback,
* get cancelled or the server closes the connection. std::function<void(bool)> wait_callback, bool recursive = false);
* Watcher(SyncClient const& client, std::string const& key,
* Returns true if the watcher is been normally cancalled, otherwise false. std::function<void(Response)> callback,
*/ std::function<void(bool)> wait_callback, bool recursive = false);
bool Wait(); Watcher(Client const& client, std::string const& key,
std::string const& range_end, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback);
Watcher(SyncClient const& client, std::string const& key,
std::string const& range_end, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback);
Watcher(Client const& client, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, bool recursive = false);
Watcher(SyncClient const& client, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, bool recursive = false);
Watcher(Client const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback);
Watcher(SyncClient const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback);
Watcher(std::string const& address, std::string const& key,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, bool recursive = false);
Watcher(std::string const& address, std::string const& key,
std::string const& range_end, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback);
Watcher(std::string const& address, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, bool recursive = false);
Watcher(std::string const& address, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback);
Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, bool recursive = false,
int const auth_token_ttl = 300);
Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
std::string const& range_end, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback,
int const auth_token_ttl = 300);
Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
int64_t fromIndex, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, bool recursive = false,
int const auth_token_ttl = 300);
Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback,
int const auth_token_ttl = 300);
Watcher(std::string const& address, std::string const& ca,
std::string const& cert, std::string const& privkey,
std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, bool recursive = false,
std::string const& target_name_override = "");
Watcher(std::string const& address, std::string const& ca,
std::string const& cert, std::string const& privkey,
std::string const& key, std::string const& range_end,
int64_t fromIndex, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback,
std::string const& target_name_override = "");
/** Watcher(Watcher const&) = delete;
* An async wait, the callback will be called when the task has been stopped. Watcher(Watcher&&) = delete;
*
* The callback parameter would be true if the watch is been normally cancalled.
*/
void Wait(std::function<void(bool)> callback);
/** /**
* Stop the watching action. * Wait util the task has been stopped, actively or passively, e.g., the
*/ * watcher get cancelled or the server closes the connection.
void Cancel(); *
* Returns true if the watcher is been normally cancelled, otherwise false.
*/
bool Wait();
~Watcher(); /**
* An async wait, the callback will be called when the task has been stopped.
*
* The callback parameter would be true if the watch is been normally
* cancelled.
*
* 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.
*
* @return true if the callback has been set successfully (no existing
* callback).
*/
bool Wait(std::function<void(bool)> callback);
protected: /**
void doWatch(std::string const & key, * Stop the watching action.
std::string const & range_end, */
std::string const & auth_token, bool Cancel();
std::function<void(Response)> callback);
int index; /**
std::function<void(Response)> callback; * Whether the watcher has been cancelled.
std::function<void(bool)> wait_callback; */
bool Cancelled() const;
// Don't use `pplx::task` to avoid sharing thread pool with other actions on the client ~Watcher();
// to avoid any potential blocking, which may block the keepalive loop and evict the lease.
std::thread task_;
struct EtcdServerStubs; protected:
struct EtcdServerStubsDeleter { void doWatch(std::string const& key, std::string const& range_end,
void operator()(etcd::Watcher::EtcdServerStubs *stubs); std::string const& auth_token,
}; std::function<void(Response)> callback);
std::unique_ptr<EtcdServerStubs, EtcdServerStubsDeleter> stubs;
private: std::function<void(Response)> callback;
int fromIndex; std::function<void(bool)> wait_callback;
bool recursive;
std::atomic_bool cancelled; // Don't use `pplx::task` to avoid sharing thread pool with other actions on
// the client to avoid any potential blocking, which may block the keepalive
// loop and evict the lease.
std::thread task_;
struct EtcdServerStubs;
struct EtcdServerStubsDeleter {
void operator()(etcd::Watcher::EtcdServerStubs* stubs);
}; };
} std::unique_ptr<EtcdServerStubs, EtcdServerStubsDeleter> stubs;
private:
int64_t fromIndex;
bool recursive;
std::atomic_bool cancelled;
};
} // namespace etcd
#endif #endif

View File

@ -2,64 +2,116 @@
#define __V3_ACTION_HPP__ #define __V3_ACTION_HPP__
#include <chrono> #include <chrono>
#include <ostream>
#include <grpc++/grpc++.h> #include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h" #include "proto/rpc.grpc.pb.h"
#include "proto/v3election.grpc.pb.h"
#include "proto/v3lock.grpc.pb.h" #include "proto/v3lock.grpc.pb.h"
#include "etcd/v3/action_constants.hpp"
using grpc::ClientContext; using grpc::ClientContext;
using grpc::CompletionQueue; using grpc::CompletionQueue;
using grpc::Status; using grpc::Status;
using etcdserverpb::Cluster;
using etcdserverpb::KV; using etcdserverpb::KV;
using etcdserverpb::Watch;
using etcdserverpb::Lease; using etcdserverpb::Lease;
using etcdserverpb::Watch;
using v3electionpb::Election;
using v3lockpb::Lock; using v3lockpb::Lock;
namespace etcdv3 namespace etcd {
{ class Response;
enum Atomicity_Type }
{
PREV_INDEX = 0,
PREV_VALUE = 1
};
struct ActionParameters namespace etcdv3 {
{ enum class AtomicityType { PREV_INDEX = 0, PREV_VALUE = 1 };
ActionParameters();
bool withPrefix;
int revision;
int old_revision;
int64_t lease_id;
int ttl;
int limit;
std::string key;
std::string range_end;
std::string value;
std::string old_value;
std::string auth_token;
KV::Stub* kv_stub;
Watch::Stub* watch_stub;
Lease::Stub* lease_stub;
Lock::Stub* lock_stub;
};
class Action struct ActionParameters {
{ ActionParameters();
public: bool withPrefix;
Action(etcdv3::ActionParameters params); int64_t revision = 0;
void waitForResponse(); int64_t old_revision = 0;
const std::chrono::high_resolution_clock::time_point startTimepoint(); int64_t lease_id = 0; // no lease
protected: int ttl;
Status status; int limit;
ClientContext context; std::string name; // for campaign (in v3election)
CompletionQueue cq_; std::string key;
etcdv3::ActionParameters parameters; std::string range_end;
std::chrono::high_resolution_clock::time_point start_timepoint; bool keys_only;
}; bool count_only;
std::string value;
std::string old_value;
std::string auth_token;
namespace detail { // for cluster management apis
std::string string_plus_one(std::string const &value); std::vector<std::string> peer_urls;
bool is_learner;
uint64_t member_id;
std::chrono::microseconds grpc_timeout = std::chrono::microseconds::zero();
KV::Stub* kv_stub;
Watch::Stub* watch_stub;
Cluster::Stub* cluster_stub;
Lease::Stub* lease_stub;
Lock::Stub* lock_stub;
Election::Stub* election_stub;
bool has_grpc_timeout() const;
std::chrono::system_clock::time_point grpc_deadline() const;
void dump(std::ostream& os) const;
};
class Action {
public:
Action(etcdv3::ActionParameters const& params);
Action(etcdv3::ActionParameters&& params);
virtual ~Action();
void waitForResponse();
const std::chrono::high_resolution_clock::time_point startTimepoint();
protected:
Status status;
ClientContext context;
CompletionQueue cq_;
etcdv3::ActionParameters parameters;
std::chrono::high_resolution_clock::time_point start_timepoint;
private:
// Init things like auth token, etc.
void InitAction();
friend class etcd::Response;
};
namespace detail {
std::string string_plus_one(std::string const& value);
std::string resolve_etcd_endpoints(std::string const& default_endpoints);
template <typename Req>
void make_request_with_ranges(Req& req, std::string const& key,
std::string const& range_end,
bool const recursive) {
if (!recursive) {
req.set_key(key);
} else {
if (key.empty()) {
req.set_key(etcdv3::NUL);
req.set_range_end(etcdv3::NUL);
} else {
req.set_key(key);
req.set_range_end(detail::string_plus_one(key));
}
}
if (!range_end.empty()) {
req.set_range_end(range_end);
} }
} }
} // namespace detail
} // namespace etcdv3
#endif #endif

View File

@ -1,27 +0,0 @@
#ifndef __ASYNC_COMPAREANDDELETE_HPP__
#define __ASYNC_COMPAREANDDELETE_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncTxnResponse.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::TxnResponse;
using etcdserverpb::KV;
namespace etcdv3
{
class AsyncCompareAndDeleteAction : public etcdv3::Action
{
public:
AsyncCompareAndDeleteAction(etcdv3::ActionParameters param, etcdv3::Atomicity_Type type);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
};
}
#endif

View File

@ -1,27 +0,0 @@
#ifndef __ASYNC_COMPAREANDSWAP_HPP__
#define __ASYNC_COMPAREANDSWAP_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncTxnResponse.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::TxnResponse;
using etcdserverpb::KV;
namespace etcdv3
{
class AsyncCompareAndSwapAction : public etcdv3::Action
{
public:
AsyncCompareAndSwapAction(etcdv3::ActionParameters param, etcdv3::Atomicity_Type type);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
};
}
#endif

View File

@ -1,26 +0,0 @@
#ifndef __ASYNC_DELETE_HPP__
#define __ASYNC_DELETE_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncDeleteRangeResponse.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::DeleteRangeResponse;
namespace etcdv3
{
class AsyncDeleteAction : public etcdv3::Action
{
public:
AsyncDeleteAction(etcdv3::ActionParameters param);
AsyncDeleteRangeResponse ParseResponse();
private:
DeleteRangeResponse reply;
std::unique_ptr<ClientAsyncResponseReader<DeleteRangeResponse>> response_reader;
};
}
#endif

View File

@ -1,23 +0,0 @@
#ifndef __ASYNC_DELETERESPONSE_HPP__
#define __ASYNC_DELETERESPONSE_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/V3Response.hpp"
#include "etcd/v3/Action.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::DeleteRangeResponse;
namespace etcdv3
{
class AsyncDeleteRangeResponse : public etcdv3::V3Response
{
public:
AsyncDeleteRangeResponse(){};
void ParseResponse(std::string const& key, bool prefix, DeleteRangeResponse& resp);
};
}
#endif

491
etcd/v3/AsyncGRPC.hpp Normal file
View File

@ -0,0 +1,491 @@
#ifndef __ASYNC_GRPC_HPP__
#define __ASYNC_GRPC_HPP__
#include <atomic>
#include <mutex>
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "proto/rpc.pb.h"
#include "proto/v3election.grpc.pb.h"
#include "proto/v3election.pb.h"
#include "proto/v3lock.grpc.pb.h"
#include "proto/v3lock.pb.h"
#include "etcd/Response.hpp"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/V3Response.hpp"
using grpc::ClientAsyncReader;
using grpc::ClientAsyncReaderWriter;
using grpc::ClientAsyncResponseReader;
using etcdserverpb::KV;
using etcdserverpb::DeleteRangeRequest;
using etcdserverpb::DeleteRangeResponse;
using etcdserverpb::LeaseCheckpointRequest;
using etcdserverpb::LeaseCheckpointResponse;
using etcdserverpb::LeaseGrantRequest;
using etcdserverpb::LeaseGrantResponse;
using etcdserverpb::LeaseKeepAliveRequest;
using etcdserverpb::LeaseKeepAliveResponse;
using etcdserverpb::LeaseLeasesRequest;
using etcdserverpb::LeaseLeasesResponse;
using etcdserverpb::LeaseRevokeRequest;
using etcdserverpb::LeaseRevokeResponse;
using etcdserverpb::LeaseTimeToLiveRequest;
using etcdserverpb::LeaseTimeToLiveResponse;
using etcdserverpb::MemberAddRequest;
using etcdserverpb::MemberAddResponse;
using etcdserverpb::MemberListRequest;
using etcdserverpb::MemberListResponse;
using etcdserverpb::MemberRemoveRequest;
using etcdserverpb::MemberRemoveResponse;
using etcdserverpb::PutRequest;
using etcdserverpb::PutResponse;
using etcdserverpb::RangeRequest;
using etcdserverpb::RangeResponse;
using etcdserverpb::TxnRequest;
using etcdserverpb::TxnResponse;
using etcdserverpb::WatchRequest;
using etcdserverpb::WatchResponse;
using v3electionpb::CampaignRequest;
using v3electionpb::CampaignResponse;
using v3electionpb::LeaderRequest;
using v3electionpb::LeaderResponse;
using v3electionpb::ProclaimRequest;
using v3electionpb::ProclaimResponse;
using v3electionpb::ResignRequest;
using v3electionpb::ResignResponse;
using v3lockpb::LockRequest;
using v3lockpb::LockResponse;
using v3lockpb::UnlockRequest;
using v3lockpb::UnlockResponse;
namespace etcd {
class KeepAlive;
}
namespace etcdv3 {
class Transaction;
}
namespace etcdv3 {
class AsyncCampaignResponse : public etcdv3::V3Response {
public:
AsyncCampaignResponse(){};
void ParseResponse(CampaignResponse& resp);
};
class AsyncDeleteResponse : public etcdv3::V3Response {
public:
AsyncDeleteResponse(){};
void ParseResponse(DeleteRangeResponse& resp);
};
class AsyncHeadResponse : public etcdv3::V3Response {
public:
AsyncHeadResponse(){};
void ParseResponse(RangeResponse& resp);
};
class AsyncLeaderResponse : public etcdv3::V3Response {
public:
AsyncLeaderResponse(){};
void ParseResponse(LeaderResponse& resp);
};
class AsyncLeaseGrantResponse : public etcdv3::V3Response {
public:
AsyncLeaseGrantResponse(){};
void ParseResponse(LeaseGrantResponse& resp);
};
class AsyncLeaseKeepAliveResponse : public etcdv3::V3Response {
public:
AsyncLeaseKeepAliveResponse(){};
void ParseResponse(LeaseKeepAliveResponse& resp);
};
class AsyncMemberAddResponse : public etcdv3::V3Response {
public:
AsyncMemberAddResponse(){};
void ParseResponse(MemberAddResponse& resp);
};
class AsyncMemberListResponse : public etcdv3::V3Response {
public:
AsyncMemberListResponse(){};
void ParseResponse(MemberListResponse& resp);
};
class AsyncMemberRemoveResponse : public etcdv3::V3Response {
public:
AsyncMemberRemoveResponse(){};
void ParseResponse(MemberRemoveResponse& resp);
};
class AsyncLeaseLeasesResponse : public etcdv3::V3Response {
public:
AsyncLeaseLeasesResponse(){};
void ParseResponse(LeaseLeasesResponse& resp);
};
class AsyncLeaseRevokeResponse : public etcdv3::V3Response {
public:
AsyncLeaseRevokeResponse(){};
void ParseResponse(LeaseRevokeResponse& resp);
};
class AsyncLeaseTimeToLiveResponse : public etcdv3::V3Response {
public:
AsyncLeaseTimeToLiveResponse(){};
void ParseResponse(LeaseTimeToLiveResponse& resp);
};
class AsyncLockResponse : public etcdv3::V3Response {
public:
AsyncLockResponse(){};
void ParseResponse(LockResponse& resp);
};
class AsyncObserveResponse : public etcdv3::V3Response {
public:
AsyncObserveResponse(){};
void ParseResponse(LeaderResponse& resp);
};
class AsyncProclaimResponse : public etcdv3::V3Response {
public:
AsyncProclaimResponse(){};
void ParseResponse(ProclaimResponse& resp);
};
class AsyncPutResponse : public etcdv3::V3Response {
public:
AsyncPutResponse(){};
void ParseResponse(PutResponse& resp);
};
class AsyncRangeResponse : public etcdv3::V3Response {
public:
AsyncRangeResponse(){};
void ParseResponse(RangeResponse& resp, bool prefix = false);
};
class AsyncResignResponse : public etcdv3::V3Response {
public:
AsyncResignResponse(){};
void ParseResponse(ResignResponse& resp);
};
class AsyncTxnResponse : public etcdv3::V3Response {
public:
AsyncTxnResponse(){};
void ParseResponse(TxnResponse& resp);
};
class AsyncUnlockResponse : public etcdv3::V3Response {
public:
AsyncUnlockResponse(){};
void ParseResponse(UnlockResponse& resp);
};
class AsyncWatchResponse : public etcdv3::V3Response {
public:
AsyncWatchResponse(){};
void ParseResponse(WatchResponse& resp);
};
} // namespace etcdv3
namespace etcdv3 {
class AsyncCampaignAction : public etcdv3::Action {
public:
AsyncCampaignAction(etcdv3::ActionParameters&& params);
AsyncCampaignResponse ParseResponse();
private:
CampaignResponse reply;
std::unique_ptr<ClientAsyncResponseReader<CampaignResponse>> response_reader;
};
class AsyncCompareAndDeleteAction : public etcdv3::Action {
public:
AsyncCompareAndDeleteAction(etcdv3::ActionParameters&& params,
etcdv3::AtomicityType type);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
};
class AsyncCompareAndSwapAction : public etcdv3::Action {
public:
AsyncCompareAndSwapAction(etcdv3::ActionParameters&& params,
etcdv3::AtomicityType type);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
};
class AsyncDeleteAction : public etcdv3::Action {
public:
AsyncDeleteAction(etcdv3::ActionParameters&& params);
AsyncDeleteResponse ParseResponse();
private:
DeleteRangeResponse reply;
std::unique_ptr<ClientAsyncResponseReader<DeleteRangeResponse>>
response_reader;
};
class AsyncHeadAction : public etcdv3::Action {
public:
AsyncHeadAction(etcdv3::ActionParameters&& params);
AsyncHeadResponse ParseResponse();
private:
RangeResponse reply;
std::unique_ptr<ClientAsyncResponseReader<RangeResponse>> response_reader;
};
class AsyncLeaderAction : public etcdv3::Action {
public:
AsyncLeaderAction(etcdv3::ActionParameters&& params);
AsyncLeaderResponse ParseResponse();
private:
LeaderResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaderResponse>> response_reader;
};
class AsyncLeaseGrantAction : public etcdv3::Action {
public:
AsyncLeaseGrantAction(etcdv3::ActionParameters&& params);
AsyncLeaseGrantResponse ParseResponse();
private:
LeaseGrantResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaseGrantResponse>>
response_reader;
};
class AsyncLeaseKeepAliveAction : public etcdv3::Action {
public:
AsyncLeaseKeepAliveAction(etcdv3::ActionParameters&& params);
AsyncLeaseKeepAliveResponse ParseResponse();
etcd::Response Refresh();
void CancelKeepAlive();
bool Cancelled() const;
private:
etcdv3::ActionParameters& mutable_parameters();
LeaseKeepAliveResponse reply;
std::unique_ptr<
ClientAsyncReaderWriter<LeaseKeepAliveRequest, LeaseKeepAliveResponse>>
stream;
LeaseKeepAliveRequest req;
std::atomic_bool isCancelled;
std::recursive_mutex protect_is_cancelled;
friend class etcd::KeepAlive;
};
class AsyncAddMemberAction : public etcdv3::Action {
public:
AsyncAddMemberAction(etcdv3::ActionParameters&& params);
AsyncMemberAddResponse ParseResponse();
private:
MemberAddResponse reply;
std::unique_ptr<ClientAsyncResponseReader<MemberAddResponse>> response_reader;
};
class AsyncListMemberAction : public etcdv3::Action {
public:
AsyncListMemberAction(etcdv3::ActionParameters&& params);
AsyncMemberListResponse ParseResponse();
private:
MemberListResponse reply;
std::unique_ptr<ClientAsyncResponseReader<MemberListResponse>>
response_reader;
};
class AsyncRemoveMemberAction : public etcdv3::Action {
public:
AsyncRemoveMemberAction(etcdv3::ActionParameters&& params);
AsyncMemberRemoveResponse ParseResponse();
private:
MemberRemoveResponse reply;
std::unique_ptr<ClientAsyncResponseReader<MemberRemoveResponse>>
response_reader;
};
class AsyncLeaseLeasesAction : public etcdv3::Action {
public:
AsyncLeaseLeasesAction(etcdv3::ActionParameters&& params);
AsyncLeaseLeasesResponse ParseResponse();
private:
LeaseLeasesResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaseLeasesResponse>>
response_reader;
};
class AsyncLeaseRevokeAction : public etcdv3::Action {
public:
AsyncLeaseRevokeAction(etcdv3::ActionParameters&& params);
AsyncLeaseRevokeResponse ParseResponse();
private:
LeaseRevokeResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaseRevokeResponse>>
response_reader;
};
class AsyncLeaseTimeToLiveAction : public etcdv3::Action {
public:
AsyncLeaseTimeToLiveAction(etcdv3::ActionParameters&& params);
AsyncLeaseTimeToLiveResponse ParseResponse();
private:
LeaseTimeToLiveResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaseTimeToLiveResponse>>
response_reader;
};
class AsyncLockAction : public etcdv3::Action {
public:
AsyncLockAction(etcdv3::ActionParameters&& params);
AsyncLockResponse ParseResponse();
private:
LockResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LockResponse>> response_reader;
};
class AsyncObserveAction : public etcdv3::Action {
public:
AsyncObserveAction(etcdv3::ActionParameters&& params);
AsyncObserveResponse ParseResponse();
void waitForResponse();
void CancelObserve();
bool Cancelled() const;
private:
LeaderResponse reply;
std::unique_ptr<ClientAsyncReader<LeaderResponse>> response_reader;
std::atomic_bool isCancelled;
std::mutex protect_is_cancelled;
};
class AsyncProclaimAction : public etcdv3::Action {
public:
AsyncProclaimAction(etcdv3::ActionParameters&& params);
AsyncProclaimResponse ParseResponse();
private:
ProclaimResponse reply;
std::unique_ptr<ClientAsyncResponseReader<ProclaimResponse>> response_reader;
};
class AsyncPutAction : public etcdv3::Action {
public:
AsyncPutAction(etcdv3::ActionParameters&& params);
AsyncPutResponse ParseResponse();
private:
PutResponse reply;
std::unique_ptr<ClientAsyncResponseReader<PutResponse>> response_reader;
};
class AsyncRangeAction : public etcdv3::Action {
public:
AsyncRangeAction(etcdv3::ActionParameters&& params);
AsyncRangeResponse ParseResponse();
private:
RangeResponse reply;
std::unique_ptr<ClientAsyncResponseReader<RangeResponse>> response_reader;
};
class AsyncResignAction : public etcdv3::Action {
public:
AsyncResignAction(etcdv3::ActionParameters&& params);
AsyncResignResponse ParseResponse();
private:
ResignResponse reply;
std::unique_ptr<ClientAsyncResponseReader<ResignResponse>> response_reader;
};
class AsyncSetAction : public etcdv3::Action {
public:
AsyncSetAction(etcdv3::ActionParameters&& params, bool create = false);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
bool isCreate;
};
class AsyncTxnAction : public etcdv3::Action {
public:
AsyncTxnAction(etcdv3::ActionParameters&& params,
etcdv3::Transaction const& tx);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
};
class AsyncUnlockAction : public etcdv3::Action {
public:
AsyncUnlockAction(etcdv3::ActionParameters&& params);
AsyncUnlockResponse ParseResponse();
private:
UnlockResponse reply;
std::unique_ptr<ClientAsyncResponseReader<UnlockResponse>> response_reader;
};
class AsyncUpdateAction : public etcdv3::Action {
public:
AsyncUpdateAction(etcdv3::ActionParameters&& params);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
};
class AsyncWatchAction : public etcdv3::Action {
public:
AsyncWatchAction(etcdv3::ActionParameters&& params);
AsyncWatchResponse ParseResponse();
void waitForResponse();
void waitForResponse(std::function<void(etcd::Response)> callback);
void CancelWatch();
bool Cancelled() const;
private:
int64_t watch_id = -1;
WatchResponse reply;
std::unique_ptr<ClientAsyncReaderWriter<WatchRequest, WatchResponse>> stream;
std::atomic_bool isCancelled;
};
} // namespace etcdv3
#endif

View File

@ -1,26 +0,0 @@
#ifndef __ASYNC_GET_HPP__
#define __ASYNC_GET_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncRangeResponse.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::RangeResponse;
namespace etcdv3
{
class AsyncGetAction : public etcdv3::Action
{
public:
AsyncGetAction(etcdv3::ActionParameters param);
AsyncRangeResponse ParseResponse();
private:
RangeResponse reply;
std::unique_ptr<ClientAsyncResponseReader<RangeResponse>> response_reader;
};
}
#endif

View File

@ -1,81 +0,0 @@
#ifndef __ASYNC_LEASEACTION_HPP__
#define __ASYNC_LEASEACTION_HPP__
#include <mutex>
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncLeaseResponse.hpp"
#include "etcd/Response.hpp"
using grpc::ClientAsyncResponseReader;
using grpc::ClientAsyncReaderWriter;
using etcdserverpb::LeaseGrantResponse;
using etcdserverpb::LeaseRevokeResponse;
using etcdserverpb::LeaseCheckpoint;
using etcdserverpb::LeaseCheckpointResponse;
using etcdserverpb::LeaseKeepAliveRequest;
using etcdserverpb::LeaseKeepAliveResponse;
using etcdserverpb::LeaseTimeToLiveResponse;
using etcdserverpb::LeaseStatus;
using etcdserverpb::LeaseLeasesResponse;
namespace etcdv3
{
class AsyncLeaseGrantAction : public etcdv3::Action {
public:
AsyncLeaseGrantAction(etcdv3::ActionParameters param);
AsyncLeaseGrantResponse ParseResponse();
private:
LeaseGrantResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaseGrantResponse>> response_reader;
};
class AsyncLeaseRevokeAction: public etcdv3::Action {
public:
AsyncLeaseRevokeAction(etcdv3::ActionParameters param);
AsyncLeaseRevokeResponse ParseResponse();
private:
LeaseRevokeResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaseRevokeResponse>> response_reader;
};
class AsyncLeaseKeepAliveAction: public etcdv3::Action {
public:
AsyncLeaseKeepAliveAction(etcdv3::ActionParameters param);
AsyncLeaseKeepAliveResponse ParseResponse();
etcd::Response Refresh();
void CancelKeepAlive();
bool Cancelled() const;
private:
LeaseKeepAliveResponse reply;
std::unique_ptr<ClientAsyncReaderWriter<LeaseKeepAliveRequest, LeaseKeepAliveResponse>> stream;
LeaseKeepAliveRequest req;
bool isCancelled;
std::mutex protect_is_cancelled;
};
class AsyncLeaseTimeToLiveAction: public etcdv3::Action {
public:
AsyncLeaseTimeToLiveAction(etcdv3::ActionParameters param);
AsyncLeaseTimeToLiveResponse ParseResponse();
private:
LeaseTimeToLiveResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaseTimeToLiveResponse>> response_reader;
};
class AsyncLeaseLeasesAction: public etcdv3::Action {
public:
AsyncLeaseLeasesAction(etcdv3::ActionParameters param);
AsyncLeaseLeasesResponse ParseResponse();
private:
LeaseLeasesResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaseLeasesResponse>> response_reader;
};
}
#endif

View File

@ -1,55 +0,0 @@
#ifndef __ASYNC_LEASERESPONSE_HPP__
#define __ASYNC_LEASERESPONSE_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/V3Response.hpp"
using etcdserverpb::LeaseGrantResponse;
using etcdserverpb::LeaseRevokeResponse;
using etcdserverpb::LeaseCheckpoint;
using etcdserverpb::LeaseCheckpointResponse;
using etcdserverpb::LeaseKeepAliveResponse;
using etcdserverpb::LeaseTimeToLiveResponse;
using etcdserverpb::LeaseStatus;
using etcdserverpb::LeaseLeasesResponse;
namespace etcdv3
{
class AsyncLeaseGrantResponse : public etcdv3::V3Response
{
public:
AsyncLeaseGrantResponse(){};
void ParseResponse(LeaseGrantResponse& resp);
};
class AsyncLeaseRevokeResponse : public etcdv3::V3Response
{
public:
AsyncLeaseRevokeResponse(){};
void ParseResponse(LeaseRevokeResponse& resp);
};
class AsyncLeaseKeepAliveResponse : public etcdv3::V3Response
{
public:
AsyncLeaseKeepAliveResponse(){};
void ParseResponse(LeaseKeepAliveResponse& resp);
};
class AsyncLeaseTimeToLiveResponse : public etcdv3::V3Response
{
public:
AsyncLeaseTimeToLiveResponse(){};
void ParseResponse(LeaseTimeToLiveResponse& resp);
};
class AsyncLeaseLeasesResponse : public etcdv3::V3Response
{
public:
AsyncLeaseLeasesResponse(){};
void ParseResponse(LeaseLeasesResponse& resp);
};
}
#endif

View File

@ -1,41 +0,0 @@
#ifndef __ASYNC_LOCKACTION_HPP__
#define __ASYNC_LOCKACTION_HPP__
#include <grpc++/grpc++.h>
#include "proto/v3lock.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncLockResponse.hpp"
#include "etcd/Response.hpp"
using grpc::ClientAsyncResponseReader;
using v3lockpb::LockRequest;
using v3lockpb::LockResponse;
using v3lockpb::UnlockRequest;
using v3lockpb::UnlockResponse;
namespace etcdv3
{
class AsyncLockAction : public etcdv3::Action
{
public:
AsyncLockAction(etcdv3::ActionParameters param);
AsyncLockResponse ParseResponse();
private:
LockResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LockResponse>> response_reader;
};
class AsyncUnlockAction : public etcdv3::Action
{
public:
AsyncUnlockAction(etcdv3::ActionParameters param);
AsyncUnlockResponse ParseResponse();
private:
UnlockResponse reply;
std::unique_ptr<ClientAsyncResponseReader<UnlockResponse>> response_reader;
};
}
#endif

View File

@ -1,33 +0,0 @@
#ifndef __ASYNC_LOCK_HPP__
#define __ASYNC_LOCK_HPP__
#include <grpc++/grpc++.h>
#include "proto/v3lock.grpc.pb.h"
#include "etcd/v3/V3Response.hpp"
using grpc::ClientAsyncResponseReader;
using v3lockpb::LockRequest;
using v3lockpb::LockResponse;
using v3lockpb::UnlockRequest;
using v3lockpb::UnlockResponse;
namespace etcdv3
{
class AsyncLockResponse : public etcdv3::V3Response
{
public:
AsyncLockResponse(){};
void ParseResponse(LockResponse& resp);
};
class AsyncUnlockResponse : public etcdv3::V3Response
{
public:
AsyncUnlockResponse(){};
void ParseResponse(UnlockResponse& resp);
};
}
#endif

View File

@ -1,22 +0,0 @@
#ifndef __ASYNC_RANGERESPONSE_HPP__
#define __ASYNC_RANGERESPONSE_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/V3Response.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::RangeResponse;
namespace etcdv3
{
class AsyncRangeResponse : public etcdv3::V3Response
{
public:
AsyncRangeResponse(){};
void ParseResponse(RangeResponse& resp, bool prefix=false);
};
}
#endif

View File

@ -1,28 +0,0 @@
#ifndef __ASYNC_SET_HPP__
#define __ASYNC_SET_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncTxnResponse.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::TxnResponse;
using etcdserverpb::KV;
namespace etcdv3
{
class AsyncSetAction : public etcdv3::Action
{
public:
AsyncSetAction(etcdv3::ActionParameters param, bool create=false);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
bool isCreate;
};
}
#endif

View File

@ -1,28 +0,0 @@
#ifndef __ASYNC_TXNACTION_HPP__
#define __ASYNC_TXNACTION_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncTxnResponse.hpp"
#include "etcd/v3/Transaction.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::TxnResponse;
using etcdserverpb::KV;
namespace etcdv3
{
class AsyncTxnAction : public etcdv3::Action
{
public:
AsyncTxnAction(etcdv3::ActionParameters param, etcdv3::Transaction const &tx);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
};
}
#endif

View File

@ -1,20 +0,0 @@
#ifndef __ASYNC_TXNRESPONSE_HPP__
#define __ASYNC_TXNRESPONSE_HPP__
#include "proto/rpc.pb.h"
#include "etcd/v3/V3Response.hpp"
using etcdserverpb::TxnResponse;
namespace etcdv3
{
class AsyncTxnResponse : public etcdv3::V3Response
{
public:
AsyncTxnResponse(){};
void ParseResponse(TxnResponse& resp);
void ParseResponse(std::string const& key, bool prefix, TxnResponse& resp);
};
}
#endif

View File

@ -1,27 +0,0 @@
#ifndef __ASYNC_UPDATE_HPP__
#define __ASYNC_UPDATE_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncTxnResponse.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::TxnResponse;
using etcdserverpb::KV;
namespace etcdv3
{
class AsyncUpdateAction : public etcdv3::Action
{
public:
AsyncUpdateAction(etcdv3::ActionParameters param);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
};
}
#endif

View File

@ -1,37 +0,0 @@
#ifndef __ASYNC_WATCHACTION_HPP__
#define __ASYNC_WATCHACTION_HPP__
#include <atomic>
#include <mutex>
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncWatchResponse.hpp"
#include "etcd/Response.hpp"
using grpc::ClientAsyncReaderWriter;
using etcdserverpb::WatchRequest;
using etcdserverpb::WatchResponse;
namespace etcdv3
{
class AsyncWatchAction : public etcdv3::Action
{
public:
AsyncWatchAction(etcdv3::ActionParameters param);
AsyncWatchResponse ParseResponse();
void waitForResponse();
void waitForResponse(std::function<void(etcd::Response)> callback);
void CancelWatch();
void WatchReq(std::string const & key);
bool Cancelled() const;
private:
WatchResponse reply;
std::unique_ptr<ClientAsyncReaderWriter<WatchRequest,WatchResponse>> stream;
std::atomic_bool isCancelled;
std::mutex protect_is_cancalled;
};
}
#endif

View File

@ -1,25 +0,0 @@
#ifndef __ASYNC_WATCH_HPP__
#define __ASYNC_WATCH_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "proto/rpc.pb.h"
#include "etcd/v3/V3Response.hpp"
using etcdserverpb::WatchRequest;
using etcdserverpb::WatchResponse;
using etcdserverpb::KV;
namespace etcdv3
{
class AsyncWatchResponse : public etcdv3::V3Response
{
public:
AsyncWatchResponse(){};
void ParseResponse(WatchResponse& resp);
};
}
#endif

View File

@ -3,18 +3,16 @@
#include "proto/kv.pb.h" #include "proto/kv.pb.h"
namespace etcdv3 {
class KeyValue {
public:
KeyValue();
mvccpb::KeyValue kvs;
void set_ttl(int ttl);
int get_ttl() const;
namespace etcdv3 private:
{ int ttl;
class KeyValue };
{ } // namespace etcdv3
public:
KeyValue();
mvccpb::KeyValue kvs;
void set_ttl(int ttl);
int get_ttl() const;
private:
int ttl;
};
}
#endif #endif

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

@ -0,0 +1,33 @@
#ifndef __V3_ETCDV3MEMBERS_HPP__
#define __V3_ETCDV3MEMBERS_HPP__
#include <cstdint>
#include <string>
#include <vector>
using namespace std;
namespace etcdv3 {
class Member {
public:
Member();
void set_id(uint64_t const& id);
uint64_t const& get_id() const;
void set_name(std::string const& name);
std::string const& get_name() const;
void set_peerURLs(std::vector<std::string> const& peerURLs);
std::vector<std::string> const& get_peerURLs() const;
void set_clientURLs(std::vector<std::string> const& clientURLs);
std::vector<std::string> const& get_clientURLs() const;
void set_learner(bool isLearner);
bool get_learner() const;
private:
uint64_t id;
std::string name;
std::vector<std::string> peerURLs;
std::vector<std::string> clientURLs;
bool isLearner;
};
} // namespace etcdv3
#endif

View File

@ -1,42 +1,223 @@
#ifndef V3_SRC_TRANSACTION_HPP_ #ifndef V3_SRC_TRANSACTION_HPP_
#define V3_SRC_TRANSACTION_HPP_ #define V3_SRC_TRANSACTION_HPP_
#include <memory>
#include <string> #include <string>
#include "txn.pb.h"
namespace etcdserverpb { namespace etcdserverpb {
class TxnRequest; class TxnRequest;
} }
namespace etcdv3 { namespace etcdv3 {
class Transaction { enum class CompareResult {
public: EQUAL = 0,
Transaction(); GREATER = 1,
Transaction(std::string const&); LESS = 2,
virtual ~Transaction(); NOT_EQUAL = 3,
void init_compare(etcdserverpb::Compare::CompareResult, etcdserverpb::Compare::CompareTarget);
void init_compare(std::string const &, etcdserverpb::Compare::CompareResult, etcdserverpb::Compare::CompareTarget);
void init_compare(int, etcdserverpb::Compare::CompareResult, etcdserverpb::Compare::CompareTarget);
void setup_basic_failure_operation(std::string const &key);
void setup_set_failure_operation(std::string const &key, std::string const &value, int64_t leaseid);
void setup_basic_create_sequence(std::string const &key, std::string const &value, int64_t leaseid);
void setup_compare_and_swap_sequence(std::string const &valueToSwap, int64_t leaseid);
void setup_delete_sequence(std::string const &key, std::string const &range_end, bool recursive);
void setup_delete_failure_operation(std::string const &key, std::string const &range_end, bool recursive);
void setup_compare_and_delete_operation(std::string const& key);
// update without `get` and no `prev_kv` returned
void setup_put(std::string const &key, std::string const &value);
void setup_delete(std::string const &key);
std::unique_ptr<etcdserverpb::TxnRequest> txn_request;
private:
std::string key;
}; };
} enum class CompareTarget {
VERSION = 0,
CREATE = 1,
MOD = 2,
VALUE = 3,
LEASE = 4,
};
class Transaction {
public:
Transaction();
~Transaction();
union Value {
int64_t version;
int64_t create_revision;
int64_t mod_revision;
std::string value;
int64_t lease;
};
void add_compare(std::string const& key, CompareTarget const& target,
CompareResult const& result, Value const& target_value,
std::string const& range_end = "");
void add_compare_version(std::string const& key, int64_t const& version,
std::string const& range_end = "");
void add_compare_version(std::string const& key, CompareResult const& result,
int64_t const& version,
std::string const& range_end = "");
void add_compare_create(std::string const& key,
int64_t const& create_revision,
std::string const& range_end = "");
void add_compare_create(std::string const& key, CompareResult const& result,
int64_t const& create_revision,
std::string const& range_end = "");
void add_compare_mod(std::string const& key, int64_t const& mod_revision,
std::string const& range_end = "");
void add_compare_mod(std::string const& key, CompareResult const& result,
int64_t const& mod_revision,
std::string const& range_end = "");
void add_compare_value(std::string const& key, std::string const& value,
std::string const& range_end = "");
void add_compare_value(std::string const& key, CompareResult const& result,
std::string const& value,
std::string const& range_end = "");
void add_compare_lease(std::string const& key, int64_t const& lease,
std::string const& range_end = "");
void add_compare_lease(std::string const& key, CompareResult const& result,
int64_t const& lease,
std::string const& range_end = "");
void add_success_range(std::string const& key,
std::string const& range_end = "",
bool const recursive = false, const int64_t limit = 0);
void add_success_put(std::string const& key, std::string const& value,
int64_t const leaseid = 0, const bool prev_kv = false);
void add_success_delete(std::string const& key,
std::string const& range_end = "",
bool const recursive = false,
const bool prev_kv = false);
void add_success_txn(const std::shared_ptr<Transaction> txn);
void add_failure_range(std::string const& key,
std::string const& range_end = "",
bool const recursive = false, const int64_t limit = 0);
void add_failure_put(std::string const& key, std::string const& value,
int64_t const leaseid = 0, const bool prev_kv = false);
void add_failure_delete(std::string const& key,
std::string const& range_end = "",
bool const recursive = false,
const bool prev_kv = false);
void add_failure_txn(const std::shared_ptr<Transaction> txn);
/**
* @brief Compare, and create if succeed. If failed, the response will
* contains the previous value in "values()" field.
*/
void setup_compare_and_create(std::string const& key,
std::string const& prev_value,
std::string const& create_key,
std::string const& value,
int64_t const leaseid = 0);
/**
* @brief Compare, or create if failed. If failed, the response will contains
* the previous value in "values()" field.
*/
void setup_compare_or_create(std::string const& key,
std::string const& prev_value,
std::string const& create_key,
std::string const& value,
int64_t const leaseid = 0);
/**
* @brief Compare, and swap if succeed. If failed, the response will contains
* the previous value in "values()" field.
*/
void setup_compare_and_swap(std::string const& key,
std::string const& prev_value,
std::string const& value,
int64_t const leaseid = 0);
/**
* @brief Compare, and swap if failed. If failed, the response will contains
* the previous value in "values()" field.
*/
void setup_compare_or_swap(std::string const& key,
std::string const& prev_value,
std::string const& value,
int64_t const leaseid = 0);
/**
* @brief Compare, and delete if succeed. If failed, the response will
* contains the previous value in "values()" field.
*/
void setup_compare_and_delete(std::string const& key,
std::string const& prev_value,
std::string const& delete_key,
std::string const& range_end = "",
const bool recursive = false);
/**
* @brief Compare, or delete if failed. If failed, the response will contains
* the previous value in "values()" field.
*/
void setup_compare_or_delete(std::string const& key,
std::string const& prev_value,
std::string const& delete_key,
std::string const& range_end = "",
const bool recursive = false);
/**
* @brief Compare, and create if succeed. If failed, the response will
* contains the previous value in "values()" field.
*/
void setup_compare_and_create(std::string const& key,
const int64_t prev_revision,
std::string const& create_key,
std::string const& value,
int64_t const leaseid = 0);
/**
* @brief Compare, or create if failed. If failed, the response will contains
* the previous value in "values()" field.
*/
void setup_compare_or_create(std::string const& key,
const int64_t prev_revision,
std::string const& create_key,
std::string const& value,
int64_t const leaseid = 0);
/**
* @brief Compare, and swap if succeed. If failed, the response will contains
* the previous value in "values()" field.
*/
void setup_compare_and_swap(std::string const& key,
const int64_t prev_revision,
std::string const& value,
int64_t const leaseid = 0);
/**
* @brief Compare, and swap if failed. If failed, the response will contains
* the previous value in "values()" field.
*/
void setup_compare_or_swap(std::string const& key,
const int64_t prev_revision,
std::string const& value,
int64_t const leaseid = 0);
/**
* @brief Compare, and delete if succeed. If failed, the response will
* contains the previous value in "values()" field.
*/
void setup_compare_and_delete(std::string const& key,
const int64_t prev_revision,
std::string const& delete_key,
std::string const& range_end = "",
const bool recursive = false);
/**
* @brief Compare, or delete if failed. If failed, the response will contains
* the previous value in "values()" field.
*/
void setup_compare_or_delete(std::string const& key,
const int64_t prev_revision,
std::string const& delete_key,
std::string const& range_end = "",
const bool recursive = false);
// Keep for backwards compatibility.
// update without `get` and no `prev_kv` returned
void setup_put(std::string const& key, std::string const& value);
void setup_delete(std::string const& key);
void setup_delete(std::string const& key, std::string const& range_end,
const bool recursive = false);
std::shared_ptr<etcdserverpb::TxnRequest> txn_request;
};
} // namespace etcdv3
#endif #endif

View File

@ -3,41 +3,66 @@
#include <grpc++/grpc++.h> #include <grpc++/grpc++.h>
#include "proto/kv.pb.h" #include "proto/kv.pb.h"
#include "proto/v3election.pb.h"
#include "etcd/v3/KeyValue.hpp" #include "etcd/v3/KeyValue.hpp"
#include "etcd/v3/Member.hpp"
namespace etcdv3 namespace etcdv3 {
{ class V3Response {
class V3Response public:
{ V3Response() : error_code(0), index(0){};
public: void set_error_code(int code);
V3Response(): error_code(0), index(0){}; int get_error_code() const;
void set_error_code(int code); std::string const& get_error_message() const;
int get_error_code() const; void set_error_message(std::string msg);
std::string const & get_error_message() const; void set_action(std::string action);
void set_error_message(std::string msg); int64_t get_index() const;
void set_action(std::string action); std::string const& get_action() const;
int get_index() const; std::vector<etcdv3::KeyValue> const& get_values() const;
std::string const & get_action() const; std::vector<etcdv3::KeyValue> const& get_prev_values() const;
std::vector<etcdv3::KeyValue> const & get_values() const; etcdv3::KeyValue const& get_value() const;
std::vector<etcdv3::KeyValue> const & get_prev_values() const; etcdv3::KeyValue const& get_prev_value() const;
etcdv3::KeyValue const & get_value() const; bool has_values() const;
etcdv3::KeyValue const & get_prev_value() const; int64_t get_compact_revision() const;
bool has_values() const; void set_compact_revision(const int64_t compact_revision);
void set_lock_key(std::string const &key); int64_t get_watch_id() const;
std::string const &get_lock_key() const; void set_watch_id(const int64_t watch_id);
std::vector<mvccpb::Event> const & get_events() const; void set_lock_key(std::string const& key);
protected: std::string const& get_lock_key() const;
int error_code; void set_name(std::string const& name);
int index; std::string const& get_name() const;
std::string error_message; std::vector<mvccpb::Event> const& get_events() const;
std::string action; uint64_t get_cluster_id() const;
etcdv3::KeyValue value; uint64_t get_member_id() const;
etcdv3::KeyValue prev_value; uint64_t get_raft_term() const;
std::vector<etcdv3::KeyValue> values; std::vector<int64_t> const& get_leases() const;
std::vector<etcdv3::KeyValue> prev_values; std::vector<etcdv3::Member> const& get_members() const;
std::string lock_key; // for lock
std::vector<mvccpb::Event> events; // for watch protected:
}; int error_code;
} int64_t index;
std::string error_message;
std::string action;
etcdv3::KeyValue value;
etcdv3::KeyValue prev_value;
std::vector<etcdv3::KeyValue> values;
std::vector<etcdv3::KeyValue> prev_values;
int64_t compact_revision = -1;
int64_t watch_id = -1;
std::string lock_key; // for lock
std::string name; // for campaign (in v3election)
std::vector<mvccpb::Event> events; // for watch
// cluster metadata
uint64_t cluster_id;
uint64_t member_id;
uint64_t raft_term;
// for lease list
std::vector<int64_t> leases;
// for member list
std::vector<etcdv3::Member> members;
};
} // namespace etcdv3
#endif #endif

View File

@ -1,18 +1,77 @@
#ifndef __ETCD_ACTION_CONSTANTS_HPP__ #ifndef __ETCD_ACTION_CONSTANTS_HPP__
#define __ETCD_ACTION_CONSTANTS_HPP__ #define __ETCD_ACTION_CONSTANTS_HPP__
namespace etcdv3 #include <string>
{
extern char const * CREATE_ACTION; namespace etcdv3 {
extern char const * UPDATE_ACTION; extern char const* CREATE_ACTION;
extern char const * SET_ACTION; extern char const* UPDATE_ACTION;
extern char const * GET_ACTION; extern char const* SET_ACTION;
extern char const * DELETE_ACTION; extern char const* GET_ACTION;
extern char const * COMPARESWAP_ACTION; extern char const* PUT_ACTION;
extern char const * COMPAREDELETE_ACTION; extern char const* DELETE_ACTION;
extern char const * LOCK_ACTION; extern char const* COMPARESWAP_ACTION;
extern char const * UNLOCK_ACTION; extern char const* COMPAREDELETE_ACTION;
extern char const * TXN_ACTION; extern char const* LOCK_ACTION;
} extern char const* UNLOCK_ACTION;
extern char const* TXN_ACTION;
extern char const* WATCH_ACTION;
extern char const* LEASEGRANT;
extern char const* LEASEREVOKE;
extern char const* LEASEKEEPALIVE;
extern char const* LEASETIMETOLIVE;
extern char const* LEASELEASES;
extern char const* ADDMEMBER;
extern char const* LISTMEMBER;
extern char const* REMOVEMEMBER;
extern char const* CAMPAIGN_ACTION;
extern char const* PROCLAIM_ACTION;
extern char const* LEADER_ACTION;
extern char const* OBSERVE_ACTION;
extern char const* RESIGN_ACTION;
extern std::string const NUL;
extern char const* KEEPALIVE_CREATE;
extern char const* KEEPALIVE_WRITE;
extern char const* KEEPALIVE_READ;
extern char const* KEEPALIVE_DONE;
extern char const* KEEPALIVE_FINISH;
extern char const* WATCH_CREATE;
extern char const* WATCH_WRITE;
extern char const* WATCH_WRITE_CANCEL;
extern char const* WATCH_WRITES_DONE;
extern char const* WATCH_FINISH;
extern char const* ELECTION_OBSERVE_CREATE;
extern char const* ELECTION_OBSERVE_FINISH;
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;
} // namespace etcdv3
#endif #endif

View File

@ -14,6 +14,7 @@ protobuf_generate_latest(
compute_generated_srcs(PROTO_GENERATES_SRCS "${PROTO_GEN_OUT_DIR}" false ${PROTO_SRCS}) compute_generated_srcs(PROTO_GENERATES_SRCS "${PROTO_GEN_OUT_DIR}" false ${PROTO_SRCS})
set(PROTO_GRPC_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/rpc.proto" set(PROTO_GRPC_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/rpc.proto"
"${CMAKE_CURRENT_SOURCE_DIR}/v3election.proto"
"${CMAKE_CURRENT_SOURCE_DIR}/v3lock.proto") "${CMAKE_CURRENT_SOURCE_DIR}/v3lock.proto")
grpc_generate_cpp(PROTO_GRPC_GENERATES PROTO_GRPC_GENERATES_HDRS grpc_generate_cpp(PROTO_GRPC_GENERATES PROTO_GRPC_GENERATES_HDRS
"${PROTO_GEN_OUT_DIR}" "${PROTO_GEN_OUT_DIR}"

View File

@ -4,7 +4,6 @@ package etcdserverpb;
import "gogoproto/gogo.proto"; import "gogoproto/gogo.proto";
import "kv.proto"; import "kv.proto";
import "auth.proto"; import "auth.proto";
import "txn.proto";
// for grpc-gateway // for grpc-gateway
import "google/api/annotations.proto"; import "google/api/annotations.proto";
@ -533,6 +532,46 @@ message ResponseOp {
} }
} }
message Compare {
enum CompareResult {
EQUAL = 0;
GREATER = 1;
LESS = 2;
NOT_EQUAL = 3;
}
enum CompareTarget {
VERSION = 0;
CREATE = 1;
MOD = 2;
VALUE = 3;
LEASE = 4;
}
// result is logical comparison operation for this comparison.
CompareResult result = 1;
// target is the key-value field to inspect for the comparison.
CompareTarget target = 2;
// key is the subject key for the comparison operation.
bytes key = 3;
oneof target_union {
// version is the version of the given key
int64 version = 4;
// create_revision is the creation revision of the given key
int64 create_revision = 5;
// mod_revision is the last modified revision of the given key.
int64 mod_revision = 6;
// value is the value of the given key, in bytes.
bytes value = 7;
// lease is the lease id of the given key.
int64 lease = 8;
// leave room for more target_union field tags, jump to 64
}
// range_end compares the given target to all keys in the range [key, range_end).
// See RangeRequest for more details on key ranges.
bytes range_end = 64;
// TODO: fill out with most of the rest of RangeRequest fields when needed.
}
// From google paxosdb paper: // From google paxosdb paper:
// Our implementation hinges around a powerful primitive which we call MultiOp. All other database // Our implementation hinges around a powerful primitive which we call MultiOp. All other database
// operations except for iteration are implemented as a single call to MultiOp. A MultiOp is applied atomically // operations except for iteration are implemented as a single call to MultiOp. A MultiOp is applied atomically

View File

@ -1,42 +0,0 @@
syntax = "proto3";
package etcdserverpb;
message Compare {
enum CompareResult {
EQUAL = 0;
GREATER = 1;
LESS = 2;
NOT_EQUAL = 3;
}
enum CompareTarget {
VERSION = 0;
CREATE = 1;
MOD = 2;
VALUE = 3;
LEASE = 4;
}
// result is logical comparison operation for this comparison.
CompareResult result = 1;
// target is the key-value field to inspect for the comparison.
CompareTarget target = 2;
// key is the subject key for the comparison operation.
bytes key = 3;
oneof target_union {
// version is the version of the given key
int64 version = 4;
// create_revision is the creation revision of the given key
int64 create_revision = 5;
// mod_revision is the last modified revision of the given key.
int64 mod_revision = 6;
// value is the value of the given key, in bytes.
bytes value = 7;
// lease is the lease id of the given key.
int64 lease = 8;
// leave room for more target_union field tags, jump to 64
}
// range_end compares the given target to all keys in the range [key, range_end).
// See RangeRequest for more details on key ranges.
bytes range_end = 64;
// TODO: fill out with most of the rest of RangeRequest fields when needed.
}

119
proto/v3election.proto Normal file
View File

@ -0,0 +1,119 @@
syntax = "proto3";
package v3electionpb;
import "gogoproto/gogo.proto";
import "rpc.proto";
import "kv.proto";
// for grpc-gateway
import "google/api/annotations.proto";
option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
// The election service exposes client-side election facilities as a gRPC interface.
service Election {
// Campaign waits to acquire leadership in an election, returning a LeaderKey
// representing the leadership if successful. The LeaderKey can then be used
// to issue new values on the election, transactionally guard API requests on
// leadership still being held, and resign from the election.
rpc Campaign(CampaignRequest) returns (CampaignResponse) {
option (google.api.http) = {
post: "/v3/election/campaign"
body: "*"
};
}
// Proclaim updates the leader's posted value with a new value.
rpc Proclaim(ProclaimRequest) returns (ProclaimResponse) {
option (google.api.http) = {
post: "/v3/election/proclaim"
body: "*"
};
}
// Leader returns the current election proclamation, if any.
rpc Leader(LeaderRequest) returns (LeaderResponse) {
option (google.api.http) = {
post: "/v3/election/leader"
body: "*"
};
}
// Observe streams election proclamations in-order as made by the election's
// elected leaders.
rpc Observe(LeaderRequest) returns (stream LeaderResponse) {
option (google.api.http) = {
post: "/v3/election/observe"
body: "*"
};
}
// Resign releases election leadership so other campaigners may acquire
// leadership on the election.
rpc Resign(ResignRequest) returns (ResignResponse) {
option (google.api.http) = {
post: "/v3/election/resign"
body: "*"
};
}
}
message CampaignRequest {
// name is the election's identifier for the campaign.
bytes name = 1;
// lease is the ID of the lease attached to leadership of the election. If the
// lease expires or is revoked before resigning leadership, then the
// leadership is transferred to the next campaigner, if any.
int64 lease = 2;
// value is the initial proclaimed value set when the campaigner wins the
// election.
bytes value = 3;
}
message CampaignResponse {
etcdserverpb.ResponseHeader header = 1;
// leader describes the resources used for holding leadereship of the election.
LeaderKey leader = 2;
}
message LeaderKey {
// name is the election identifier that correponds to the leadership key.
bytes name = 1;
// key is an opaque key representing the ownership of the election. If the key
// is deleted, then leadership is lost.
bytes key = 2;
// rev is the creation revision of the key. It can be used to test for ownership
// of an election during transactions by testing the key's creation revision
// matches rev.
int64 rev = 3;
// lease is the lease ID of the election leader.
int64 lease = 4;
}
message LeaderRequest {
// name is the election identifier for the leadership information.
bytes name = 1;
}
message LeaderResponse {
etcdserverpb.ResponseHeader header = 1;
// kv is the key-value pair representing the latest leader update.
mvccpb.KeyValue kv = 2;
}
message ResignRequest {
// leader is the leadership to relinquish by resignation.
LeaderKey leader = 1;
}
message ResignResponse {
etcdserverpb.ResponseHeader header = 1;
}
message ProclaimRequest {
// leader is the leadership hold on the election.
LeaderKey leader = 1;
// value is an update meant to overwrite the leader's current value.
bytes value = 2;
}
message ProclaimResponse {
etcdserverpb.ResponseHeader header = 1;
}

View File

@ -1,31 +1,86 @@
file(GLOB_RECURSE CPP_CLIENT_SRC # grpc stuffs
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/**/*.cpp")
set_source_files_properties(${PROTOBUF_GENERATES} PROPERTIES GENERATED TRUE) set_source_files_properties(${PROTOBUF_GENERATES} PROPERTIES GENERATED TRUE)
add_library(etcd-cpp-api ${CPP_CLIENT_SRC} ${PROTOBUF_GENERATES})
add_dependencies(etcd-cpp-api protobuf_generates)
set_property(TARGET etcd-cpp-api PROPERTY CXX_STANDARD 11)
target_link_libraries(etcd-cpp-api PUBLIC macro(include_generated_protobuf_files target)
${Boost_LIBRARIES} target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen)
${CPPREST_LIB} target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen/proto)
${PROTOBUF_LIBRARIES} endmacro()
# prepare common objects
file(GLOB_RECURSE CPP_CLIENT_CORE_SRC
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/KeepAlive.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Response.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/SyncClient.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Value.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Watcher.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/**/*.cpp"
)
add_library(etcd-cpp-api-core-objects OBJECT ${CPP_CLIENT_CORE_SRC} ${PROTOBUF_GENERATES})
use_cxx(etcd-cpp-api-core-objects)
set_exceptions(etcd-cpp-api-core-objects)
add_dependencies(etcd-cpp-api-core-objects protobuf_generates)
include_generated_protobuf_files(etcd-cpp-api-core-objects)
target_link_libraries(etcd-cpp-api-core-objects PUBLIC
${OPENSSL_LIBRARIES} ${OPENSSL_LIBRARIES}
${GRPC_LIBRARIES}) ${GRPC_LIBRARIES}
)
if(TARGET protobuf::libprotobuf)
target_link_libraries(etcd-cpp-api-core-objects PUBLIC protobuf::libprotobuf)
else()
target_link_libraries(etcd-cpp-api-core-objects PUBLIC ${PROTOBUF_LIBRARIES})
endif()
target_include_directories(etcd-cpp-api PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen) if(BUILD_ETCD_CORE_ONLY)
target_include_directories(etcd-cpp-api PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen/proto) # add the core library, includes the sycnhronous client only
add_library(etcd-cpp-api-core $<TARGET_OBJECTS:etcd-cpp-api-core-objects>)
use_cxx(etcd-cpp-api-core)
set_exceptions(etcd-cpp-api-core)
target_link_libraries(etcd-cpp-api-core PUBLIC
${OPENSSL_LIBRARIES}
${GRPC_LIBRARIES}
)
if(TARGET protobuf::libprotobuf)
target_link_libraries(etcd-cpp-api-core PUBLIC protobuf::libprotobuf)
else()
target_link_libraries(etcd-cpp-api-core PUBLIC ${PROTOBUF_LIBRARIES})
endif()
include_generated_protobuf_files(etcd-cpp-api-core)
else()
# add the client with asynchronus client
add_library(etcd-cpp-api $<TARGET_OBJECTS:etcd-cpp-api-core-objects>
"${CMAKE_CURRENT_SOURCE_DIR}/Client.cpp")
use_cxx(etcd-cpp-api)
set_exceptions(etcd-cpp-api)
target_link_libraries(etcd-cpp-api PUBLIC
${CPPREST_LIB} # n.b.: the asynchronous client requires pplx in cpprestsdk
${OPENSSL_LIBRARIES}
${GRPC_LIBRARIES}
)
if(TARGET protobuf::libprotobuf)
target_link_libraries(etcd-cpp-api PUBLIC protobuf::libprotobuf)
else()
target_link_libraries(etcd-cpp-api PUBLIC ${PROTOBUF_LIBRARIES})
endif()
include_generated_protobuf_files(etcd-cpp-api)
endif()
if(BUILD_ETCD_CORE_ONLY)
add_library(etcd-cpp-api INTERFACE)
target_link_libraries(etcd-cpp-api INTERFACE etcd-cpp-api-core)
else()
add_library(etcd-cpp-api-core INTERFACE)
target_link_libraries(etcd-cpp-api-core INTERFACE etcd-cpp-api)
endif()
if("${CMAKE_VERSION}" VERSION_LESS "3.14") if("${CMAKE_VERSION}" VERSION_LESS "3.14")
install(TARGETS etcd-cpp-api install(TARGETS etcd-cpp-api-core etcd-cpp-api
EXPORT etcd-targets EXPORT etcd-targets
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
LIBRARY DESTINATION lib LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib) ARCHIVE DESTINATION lib)
else() else()
install(TARGETS etcd-cpp-api install(TARGETS etcd-cpp-api-core etcd-cpp-api
EXPORT etcd-targets) EXPORT etcd-targets)
endif() endif()

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,15 @@
#include <chrono> #include <chrono>
#include <iostream>
#include <ratio>
#include "etcd/KeepAlive.hpp" #include "etcd/KeepAlive.hpp"
#include "etcd/v3/AsyncLeaseAction.hpp" #include "etcd/v3/AsyncGRPC.hpp"
#include <grpc++/grpc++.h> #include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h" #include "proto/rpc.grpc.pb.h"
namespace etcdv3 { namespace etcdv3 {
class AsyncLeaseKeepAliveAction; class AsyncLeaseKeepAliveAction;
} }
struct etcd::KeepAlive::EtcdServerStubs { struct etcd::KeepAlive::EtcdServerStubs {
@ -15,108 +17,144 @@ struct etcd::KeepAlive::EtcdServerStubs {
std::unique_ptr<etcdv3::AsyncLeaseKeepAliveAction> call; std::unique_ptr<etcdv3::AsyncLeaseKeepAliveAction> call;
}; };
void etcd::KeepAlive::EtcdServerStubsDeleter::operator()(etcd::KeepAlive::EtcdServerStubs *stubs) { void etcd::KeepAlive::EtcdServerStubsDeleter::operator()(
etcd::KeepAlive::EtcdServerStubs* stubs) {
if (stubs) { if (stubs) {
delete stubs; delete stubs;
} }
} }
etcd::KeepAlive::KeepAlive(Client const &client, int ttl, int64_t lease_id): etcd::KeepAlive::KeepAlive(SyncClient const& client, int ttl, int64_t lease_id)
ttl(ttl), lease_id(lease_id), continue_next(true) { : ttl(ttl),
lease_id(lease_id),
continue_next(true),
grpc_timeout(client.get_grpc_timeout()) {
if (ttl > 0 && lease_id == 0) {
this->lease_id =
const_cast<SyncClient&>(client).leasegrant(ttl).value().lease();
}
stubs.reset(new EtcdServerStubs{}); stubs.reset(new EtcdServerStubs{});
stubs->leaseServiceStub = Lease::NewStub(client.channel); stubs->leaseServiceStub = Lease::NewStub(client.grpc_channel());
etcdv3::ActionParameters params; etcdv3::ActionParameters params;
params.auth_token.assign(client.auth_token); params.auth_token.assign(client.current_auth_token());
params.grpc_timeout = grpc_timeout;
params.lease_id = this->lease_id; params.lease_id = this->lease_id;
params.lease_stub = stubs->leaseServiceStub.get(); params.lease_stub = stubs->leaseServiceStub.get();
continue_next.store(true); continue_next.store(true);
stubs->call.reset(new etcdv3::AsyncLeaseKeepAliveAction(params)); stubs->call.reset(new etcdv3::AsyncLeaseKeepAliveAction(std::move(params)));
task_ = std::thread([this]() { refresh_task_ = std::thread([this]() {
#ifndef _ETCD_NO_EXCEPTIONS
try { try {
// start refresh // start refresh
this->refresh(); this->refresh();
context.run(); } catch (const std::exception& e) {
} catch (...) { // propagate the exception
eptr_ = std::current_exception(); eptr_ = std::current_exception();
} }
context.stop(); // clean up #else
const std::string err = this->refresh();
if (!err.empty()) {
eptr_ = std::make_exception_ptr(std::runtime_error(err));
}
#endif
}); });
} }
etcd::KeepAlive::KeepAlive(std::string const & address, int ttl, int64_t lease_id): etcd::KeepAlive::KeepAlive(std::string const& address, int ttl,
KeepAlive(Client(address), ttl, lease_id) { int64_t lease_id)
} : KeepAlive(SyncClient(address), ttl, lease_id) {}
etcd::KeepAlive::KeepAlive(std::string const & address, etcd::KeepAlive::KeepAlive(std::string const& address,
std::string const & username, std::string const & password, std::string const& username,
int ttl, int64_t lease_id): std::string const& password, int ttl,
KeepAlive(Client(address, username, password), ttl, lease_id) { int64_t lease_id, int const auth_token_ttl)
} : KeepAlive(SyncClient(address, username, password, auth_token_ttl), ttl,
lease_id) {}
etcd::KeepAlive::KeepAlive(Client const &client, etcd::KeepAlive::KeepAlive(
std::function<void (std::exception_ptr)> const &handler, std::string const& address, std::string const& ca, std::string const& cert,
int ttl, int64_t lease_id): std::string const& privkey,
handler_(handler), ttl(ttl), lease_id(lease_id), continue_next(true) { std::function<void(std::exception_ptr)> const& handler, int ttl,
int64_t lease_id, std::string const& target_name_override)
: KeepAlive(SyncClient(address, ca, cert, privkey, target_name_override),
handler, ttl, lease_id) {}
etcd::KeepAlive::KeepAlive(
SyncClient const& client,
std::function<void(std::exception_ptr)> const& handler, int ttl,
int64_t lease_id)
: handler_(handler),
ttl(ttl),
lease_id(lease_id),
continue_next(true),
grpc_timeout(client.get_grpc_timeout()) {
if (ttl > 0 && lease_id == 0) {
this->lease_id =
const_cast<SyncClient&>(client).leasegrant(ttl).value().lease();
}
stubs.reset(new EtcdServerStubs{}); stubs.reset(new EtcdServerStubs{});
stubs->leaseServiceStub = Lease::NewStub(client.channel); stubs->leaseServiceStub = Lease::NewStub(client.grpc_channel());
etcdv3::ActionParameters params; etcdv3::ActionParameters params;
params.auth_token.assign(client.auth_token); params.auth_token.assign(client.current_auth_token());
// n.b.: keepalive: no need for timeout
params.lease_id = this->lease_id; params.lease_id = this->lease_id;
params.lease_stub = stubs->leaseServiceStub.get(); params.lease_stub = stubs->leaseServiceStub.get();
stubs->call.reset(new etcdv3::AsyncLeaseKeepAliveAction(params)); stubs->call.reset(new etcdv3::AsyncLeaseKeepAliveAction(std::move(params)));
task_ = std::thread([this]() { refresh_task_ = std::thread([this]() {
#ifndef _ETCD_NO_EXCEPTIONS
try { try {
// start refresh // start refresh
this->refresh(); this->refresh();
context.run();
} catch (...) { } catch (...) {
if (handler_) { // propagate the exception
handler_(std::current_exception()); eptr_ = std::current_exception();
} else { }
eptr_ = std::current_exception(); #else
} const std::string err = this->refresh();
this->Cancel(); if (!err.empty()) {
eptr_ = std::make_exception_ptr(std::runtime_error(err));
}
#endif
if (eptr_ && handler_) {
handler_(eptr_);
} }
}); });
} }
etcd::KeepAlive::KeepAlive(std::string const & address, etcd::KeepAlive::KeepAlive(
std::function<void (std::exception_ptr)> const &handler, std::string const& address,
int ttl, int64_t lease_id): std::function<void(std::exception_ptr)> const& handler, int ttl,
KeepAlive(Client(address), handler, ttl, lease_id) { int64_t lease_id)
} : KeepAlive(SyncClient(address), handler, ttl, lease_id) {}
etcd::KeepAlive::KeepAlive(std::string const & address, etcd::KeepAlive::KeepAlive(
std::string const & username, std::string const & password, std::string const& address, std::string const& username,
std::function<void (std::exception_ptr)> const &handler, std::string const& password,
int ttl, int64_t lease_id): std::function<void(std::exception_ptr)> const& handler, int ttl,
KeepAlive(Client(address, username, password), handler, ttl, lease_id) { int64_t lease_id, const int auth_token_ttl)
} : KeepAlive(SyncClient(address, username, password, auth_token_ttl),
handler, ttl, lease_id) {}
etcd::KeepAlive::~KeepAlive() etcd::KeepAlive::~KeepAlive() { this->Cancel(); }
{
this->Cancel();
}
void etcd::KeepAlive::Cancel() void etcd::KeepAlive::Cancel() {
{
if (!continue_next.exchange(false)) { if (!continue_next.exchange(false)) {
return; return;
} }
stubs->call->CancelKeepAlive();
if (keepalive_timer_) {
keepalive_timer_->cancel();
}
// clean up // stop the thread
context.stop(); cv_for_refresh_.notify_all();
if (task_.joinable()) { refresh_task_.join();
task_.join();
// send a cancel request
{
std::lock_guard<std::mutex> lock(mutex_for_refresh_);
stubs->call->CancelKeepAlive();
} }
} }
@ -124,35 +162,97 @@ void etcd::KeepAlive::Check() {
if (eptr_) { if (eptr_) {
std::rethrow_exception(eptr_); std::rethrow_exception(eptr_);
} }
// issue an refresh to make sure it still alive
#ifndef _ETCD_NO_EXCEPTIONS
try {
this->refresh_once();
} catch (...) {
// run canceller first
this->Cancel();
// propagate the exception, as we throw in `Check()`, the `handler` won't be
// touched
eptr_ = std::current_exception();
}
#else
const std::string err = this->refresh_once();
if (!err.empty()) {
// run canceller first
this->Cancel();
// propagate the exception, as we throw in `Check()`, the `handler` won't be
// touched
eptr_ = std::make_exception_ptr(std::runtime_error(err));
}
#endif
if (eptr_ && handler_) {
handler_(eptr_);
}
#ifndef _ETCD_NO_EXCEPTIONS
if (eptr_) {
// rethrow in `Check()` to keep the consistent semantics
std::rethrow_exception(eptr_);
}
#endif
return;
} }
void etcd::KeepAlive::refresh() std::string etcd::KeepAlive::refresh() {
{ while (true) {
if (!continue_next.load()) { if (!continue_next.load()) {
return; return std::string{};
} }
// minimal resolution: 1 second // minimal resolution: 1 second
int keepalive_ttl = std::max(ttl - 1, 1); int keepalive_ttl = std::max(ttl - 1, 1);
keepalive_timer_.reset(new boost::asio::steady_timer( {
context, std::chrono::seconds(keepalive_ttl))); std::unique_lock<std::mutex> lock(mutex_for_refresh_);
keepalive_timer_->async_wait([this](const boost::system::error_code& error) { if (cv_for_refresh_.wait_for(lock, std::chrono::seconds(keepalive_ttl)) ==
if (error) { std::cv_status::no_timeout) {
if (!continue_next.load()) {
return std::string{};
}
#ifndef NDEBUG #ifndef NDEBUG
std::cerr << "keepalive timer cancelled: " << error << ", " << error.message() << std::endl; std::cerr
<< "[warn] awaked from condition_variable but continue_next is "
"not set, maybe due to clock drift."
<< std::endl;
#endif #endif
} else {
if (this->continue_next.load()) {
auto resp = this->stubs->call->Refresh();
if (!resp.is_ok()) {
throw std::runtime_error("Failed to refresh lease: error code: " + std::to_string(resp.error_code()) +
", message: " + resp.error_message());
}
if (resp.value().ttl() == 0) {
throw std::out_of_range("Failed to refresh lease due to expiration: the new TTL is 0.");
}
// trigger the next round;
this->refresh();
} }
} }
});
// execute refresh
const std::string err = this->refresh_once();
if (!err.empty()) {
return err;
}
}
return std::string{};
}
std::string etcd::KeepAlive::refresh_once() {
std::lock_guard<std::mutex> scope_lock(mutex_for_refresh_);
if (!continue_next.load()) {
return std::string{};
}
this->stubs->call->mutable_parameters().grpc_timeout = this->grpc_timeout;
auto resp = this->stubs->call->Refresh();
if (!resp.is_ok()) {
const std::string err = "Failed to refresh lease: error code: " +
std::to_string(resp.error_code()) +
", message: " + resp.error_message();
#ifndef _ETCD_NO_EXCEPTIONS
throw std::runtime_error(err);
#endif
return err;
}
if (resp.value().ttl() == 0) {
const std::string err =
"Failed to refresh lease due to expiration: the new TTL is 0.";
#ifndef _ETCD_NO_EXCEPTIONS
throw std::out_of_range(err);
#endif
return err;
}
return std::string{};
} }

View File

@ -3,118 +3,138 @@
#include <iostream> #include <iostream>
etcd::Response::Response() : _error_code(0), _index(0) {}
etcd::Response::Response(const etcdv3::V3Response& reply, std::chrono::microseconds const& duration) etcd::Response::Response(const etcd::Response& response) {
{ this->_error_code = response._error_code;
this->_error_message = response._error_message;
this->_index = response._index;
this->_action = response._action;
this->_value = response._value;
this->_prev_value = response._prev_value;
this->_values = response._values;
this->_keys = response._keys;
this->_compact_revision = response._compact_revision;
this->_lock_key = response._lock_key;
this->_name = response._name;
this->_events = response._events;
this->_duration = response._duration;
this->_cluster_id = response._cluster_id;
this->_member_id = response._member_id;
this->_raft_term = response._raft_term;
this->_leases = response._leases;
this->_members = response._members;
}
etcd::Response::Response(const etcdv3::V3Response& reply,
std::chrono::microseconds const& duration) {
_index = reply.get_index(); _index = reply.get_index();
_action = reply.get_action(); _action = reply.get_action();
_error_code = reply.get_error_code(); _error_code = reply.get_error_code();
_error_message = reply.get_error_message(); _error_message = reply.get_error_message();
if(reply.has_values()) if (reply.has_values()) {
{
auto val = reply.get_values(); auto val = reply.get_values();
for(unsigned int index = 0; index < val.size(); index++) for (unsigned int index = 0; index < val.size(); index++) {
{
_values.push_back(Value(val[index])); _values.push_back(Value(val[index]));
_keys.push_back(val[index].kvs.key()); _keys.push_back(val[index].kvs.key());
} }
} _value = Value(reply.get_values()[0]);
else } else {
{
_value = Value(reply.get_value()); _value = Value(reply.get_value());
} }
_prev_value = Value(reply.get_prev_value()); _prev_value = Value(reply.get_prev_value());
_compact_revision = reply.get_compact_revision();
_watch_id = reply.get_watch_id();
_lock_key = reply.get_lock_key(); _lock_key = reply.get_lock_key();
_events = reply.get_events(); _name = reply.get_name();
for (auto const& ev : reply.get_events()) {
_events.emplace_back(etcd::Event(ev));
}
// duration // duration
_duration = duration; _duration = duration;
// etcd head
_cluster_id = reply.get_cluster_id();
_member_id = reply.get_member_id();
_raft_term = reply.get_raft_term();
// lease list
this->_leases = reply.get_leases();
// member list
this->_members = reply.get_members();
} }
etcd::Response::Response(int error_code, std::string const& error_message)
: _error_code(error_code), _error_message(error_message), _index(0) {}
etcd::Response::Response() etcd::Response::Response(int error_code, char const* error_message)
: _error_code(0), : _error_code(error_code), _error_message(error_message), _index(0) {}
_index(0)
{
}
etcd::Response::Response(int error_code, char const * error_message) int etcd::Response::error_code() const { return _error_code; }
: _error_code(error_code),
_error_message(error_message),
_index(0)
{
}
int etcd::Response::error_code() const std::string const& etcd::Response::error_message() const {
{
return _error_code;
}
std::string const & etcd::Response::error_message() const
{
return _error_message; return _error_message;
} }
int etcd::Response::index() const bool etcd::Response::is_ok() const { return error_code() == 0; }
{
return _index;
}
std::string const & etcd::Response::action() const bool etcd::Response::is_network_unavailable() const {
{
return _action;
}
bool etcd::Response::is_ok() const
{
return error_code() == 0;
}
bool etcd::Response::is_network_unavailable() const
{
return error_code() == ::grpc::StatusCode::UNAVAILABLE; return error_code() == ::grpc::StatusCode::UNAVAILABLE;
} }
etcd::Value const & etcd::Response::value() const bool etcd::Response::is_grpc_timeout() const {
{ return _error_code == grpc::StatusCode::DEADLINE_EXCEEDED;
return _value;
} }
etcd::Value const & etcd::Response::prev_value() const std::string const& etcd::Response::action() const { return _action; }
{
return _prev_value;
}
etcd::Values const & etcd::Response::values() const int64_t etcd::Response::index() const { return _index; }
{
return _values;
}
etcd::Value const & etcd::Response::value(int index) const etcd::Value const& etcd::Response::value() const { return _value; }
{
etcd::Value const& etcd::Response::prev_value() const { return _prev_value; }
etcd::Values const& etcd::Response::values() const { return _values; }
etcd::Value const& etcd::Response::value(int index) const {
return _values[index]; return _values[index];
} }
etcd::Keys const & etcd::Response::keys() const etcd::Keys const& etcd::Response::keys() const { return _keys; }
{
return _keys;
}
std::string const & etcd::Response::key(int index) const std::string const& etcd::Response::key(int index) const { return _keys[index]; }
{
return _keys[index];
}
std::string const & etcd::Response::lock_key() const { int64_t etcd::Response::compact_revision() const { return _compact_revision; }
return _lock_key;
}
std::vector<mvccpb::Event> const & etcd::Response::events() const { int64_t etcd::Response::watch_id() const { return _watch_id; }
std::string const& etcd::Response::lock_key() const { return _lock_key; }
std::string const& etcd::Response::name() const { return _name; }
std::vector<etcd::Event> const& etcd::Response::events() const {
return this->_events; return this->_events;
} }
std::chrono::microseconds const& etcd::Response::duration() const { std::chrono::microseconds const& etcd::Response::duration() const {
return this->_duration; return this->_duration;
} }
uint64_t etcd::Response::cluster_id() const { return this->_cluster_id; }
uint64_t etcd::Response::member_id() const { return this->_member_id; }
uint64_t etcd::Response::raft_term() const { return this->_raft_term; }
std::vector<int64_t> const& etcd::Response::leases() const {
return this->_leases;
}
std::vector<etcdv3::Member> const& etcd::Response::members() const {
return this->_members;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,60 +1,116 @@
#include <cstdint>
#include <iomanip> #include <iomanip>
#include "etcd/Value.hpp" #include "etcd/Value.hpp"
#include "etcd/v3/KeyValue.hpp" #include "etcd/v3/KeyValue.hpp"
etcd::Value::Value() etcd::Value::Value()
: dir(false), : dir(false), created(0), modified(0), _version(0), _ttl(0), leaseId(0) {}
created(0),
modified(0),
_ttl(0),
leaseId(0)
{
}
etcd::Value::Value(etcdv3::KeyValue const& kv) {
etcd::Value::Value(etcdv3::KeyValue const & kv) dir = false;
{ _key = kv.kvs.key();
dir=false; value = kv.kvs.value();
_key=kv.kvs.key(); created = kv.kvs.create_revision();
value=kv.kvs.value(); modified = kv.kvs.mod_revision();
created=kv.kvs.create_revision(); _version = kv.kvs.version();
modified=kv.kvs.mod_revision();
leaseId = kv.kvs.lease(); leaseId = kv.kvs.lease();
_ttl = kv.get_ttl(); _ttl = kv.get_ttl();
} }
std::string const & etcd::Value::key() const etcd::Value::Value(mvccpb::KeyValue const& kv) {
{ dir = false;
return _key; _key = kv.key();
value = kv.value();
created = kv.create_revision();
modified = kv.mod_revision();
_version = kv.version();
leaseId = kv.lease();
_ttl = -1;
} }
bool etcd::Value::is_dir() const std::string const& etcd::Value::key() const { return _key; }
{
return dir; bool etcd::Value::is_dir() const { return dir; }
std::string const& etcd::Value::as_string() const { return value; }
int64_t etcd::Value::created_index() const { return created; }
int64_t etcd::Value::modified_index() const { return modified; }
int64_t etcd::Value::version() const { return _version; }
int etcd::Value::ttl() const { return _ttl; }
int64_t etcd::Value::lease() const { return leaseId; }
std::ostream& etcd::operator<<(std::ostream& os, const etcd::Value& value) {
os << "Event: {";
os << "Key: " << value.key() << ", ";
os << "Value: " << value.as_string() << ", ";
os << "Created: " << value.created_index() << ", ";
os << "Modified: " << value.modified_index() << ", ";
os << "Version: " << value.version() << ", ";
os << "TTL: " << value.ttl() << ", ";
os << "Lease: " << value.lease() << ", ";
os << "}";
return os;
} }
std::string const & etcd::Value::as_string() const etcd::Event::Event(mvccpb::Event const& event) {
{ _has_kv = event.has_kv();
return value; _has_prev_kv = event.has_prev_kv();
if (_has_kv) {
_kv = Value(event.kv());
}
if (_has_prev_kv) {
_prev_kv = Value(event.prev_kv());
}
if (event.type() == mvccpb::Event::PUT) {
event_type_ = EventType::PUT;
} else if (event.type() == mvccpb::Event::DELETE_) {
event_type_ = EventType::DELETE_;
} else {
event_type_ = EventType::INVALID;
}
} }
int etcd::Value::created_index() const enum etcd::Event::EventType etcd::Event::event_type() const {
{ return event_type_;
return created;
} }
int etcd::Value::modified_index() const bool etcd::Event::has_kv() const { return _has_kv; }
{
return modified; bool etcd::Event::has_prev_kv() const { return _has_prev_kv; }
const etcd::Value& etcd::Event::kv() const { return _kv; }
const etcd::Value& etcd::Event::prev_kv() const { return _prev_kv; }
std::ostream& etcd::operator<<(std::ostream& os,
const etcd::Event::EventType& value) {
switch (value) {
case etcd::Event::EventType::PUT:
os << "PUT";
break;
case etcd::Event::EventType::DELETE_:
os << "DELETE";
break;
case etcd::Event::EventType::INVALID:
os << "INVALID";
break;
}
return os;
} }
int etcd::Value::ttl() const std::ostream& etcd::operator<<(std::ostream& os, const etcd::Event& event) {
{ os << "Event type: " << event.event_type();
return _ttl; if (event.has_kv()) {
} os << ", KV: " << event.kv();
}
int64_t etcd::Value::lease() const if (event.has_prev_kv()) {
{ os << ", Prev KV: " << event.prev_kv();
return leaseId; }
return os;
} }

View File

@ -1,102 +1,226 @@
#include "etcd/Watcher.hpp" #include "etcd/Watcher.hpp"
#include "etcd/v3/AsyncWatchAction.hpp"
#include "etcd/SyncClient.hpp"
#include "etcd/v3/AsyncGRPC.hpp"
struct etcd::Watcher::EtcdServerStubs { struct etcd::Watcher::EtcdServerStubs {
std::unique_ptr<Watch::Stub> watchServiceStub; std::unique_ptr<Watch::Stub> watchServiceStub;
std::unique_ptr<etcdv3::AsyncWatchAction> call; std::unique_ptr<etcdv3::AsyncWatchAction> call;
}; };
void etcd::Watcher::EtcdServerStubsDeleter::operator()(etcd::Watcher::EtcdServerStubs *stubs) { void etcd::Watcher::EtcdServerStubsDeleter::operator()(
etcd::Watcher::EtcdServerStubs* stubs) {
if (stubs) { if (stubs) {
if (stubs->watchServiceStub) {
stubs->watchServiceStub.reset();
}
if (stubs->call) {
stubs->call.reset();
}
delete stubs; delete stubs;
} }
} }
etcd::Watcher::Watcher(Client const &client, std::string const & key, etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::function<void(Response)> callback, bool recursive): std::function<void(Response)> callback,
Watcher(client, key, -1, callback, recursive) { std::function<void(bool)> wait_callback, bool recursive)
} : Watcher(client, key, -1, callback, wait_callback, recursive) {}
etcd::Watcher::Watcher(Client const &client, std::string const & key, etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::string const &range_end, std::string const& range_end,
std::function<void(Response)> callback): std::function<void(Response)> callback,
Watcher(client, key, range_end, -1, callback) { std::function<void(bool)> wait_callback)
} : Watcher(client, key, range_end, -1, callback, wait_callback) {}
etcd::Watcher::Watcher(Client const &client, std::string const & key, int fromIndex, etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::function<void(Response)> callback, bool recursive): int64_t fromIndex,
fromIndex(fromIndex), recursive(recursive) { std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, bool recursive)
: wait_callback(wait_callback), fromIndex(fromIndex), recursive(recursive) {
stubs.reset(new EtcdServerStubs{}); stubs.reset(new EtcdServerStubs{});
stubs->watchServiceStub = Watch::NewStub(client.channel); stubs->watchServiceStub = Watch::NewStub(client.channel);
doWatch(key, "", client.auth_token, callback); doWatch(key, "", client.current_auth_token(), callback);
} }
etcd::Watcher::Watcher(Client const &client, std::string const & key, etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::string const &range_end, int fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback): std::function<void(Response)> callback,
fromIndex(fromIndex), recursive(false) { std::function<void(bool)> wait_callback)
: wait_callback(wait_callback), fromIndex(fromIndex), recursive(false) {
stubs.reset(new EtcdServerStubs{}); stubs.reset(new EtcdServerStubs{});
stubs->watchServiceStub = Watch::NewStub(client.channel); stubs->watchServiceStub = Watch::NewStub(client.channel);
doWatch(key, range_end, client.auth_token, callback); doWatch(key, range_end, client.current_auth_token(), callback);
} }
etcd::Watcher::Watcher(std::string const & address, std::string const & key, etcd::Watcher::Watcher(std::string const& address, std::string const& key,
std::function<void(Response)> callback, bool recursive): std::function<void(Response)> callback,
Watcher(address, key, -1, callback, recursive) { std::function<void(bool)> wait_callback, bool recursive)
} : Watcher(address, key, -1, callback, wait_callback, recursive) {}
etcd::Watcher::Watcher(std::string const & address, std::string const & key, etcd::Watcher::Watcher(std::string const& address, std::string const& key,
std::string const & range_end, std::string const& range_end,
std::function<void(Response)> callback): std::function<void(Response)> callback,
Watcher(address, key, range_end, -1, callback) { std::function<void(bool)> wait_callback)
} : Watcher(address, key, range_end, -1, callback, wait_callback) {}
etcd::Watcher::Watcher(std::string const & address, std::string const & key, int fromIndex, etcd::Watcher::Watcher(std::string const& address, std::string const& key,
std::function<void(Response)> callback, bool recursive): int64_t fromIndex,
Watcher(Client(address), key, fromIndex, callback, recursive) { std::function<void(Response)> callback,
} std::function<void(bool)> wait_callback, bool recursive)
: Watcher(SyncClient(address), key, fromIndex, callback, wait_callback,
recursive) {}
etcd::Watcher::Watcher(std::string const & address, std::string const & key, etcd::Watcher::Watcher(std::string const& address, std::string const& key,
std::string const & range_end, int fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback): std::function<void(Response)> callback,
Watcher(Client(address), key, range_end, fromIndex, callback) { std::function<void(bool)> wait_callback)
} : Watcher(SyncClient(address), key, range_end, fromIndex, callback,
wait_callback) {}
etcd::Watcher::Watcher(std::string const & address, etcd::Watcher::Watcher(std::string const& address, std::string const& username,
std::string const & username, std::string const & password, std::string const& password, std::string const& key,
std::string const & key, std::function<void(Response)> callback,
std::function<void(Response)> callback, bool recursive): std::function<void(bool)> wait_callback, bool recursive,
Watcher(address, username, password, key, -1, callback, recursive) { int const auth_token_ttl)
} : Watcher(address, username, password, key, -1, callback, wait_callback,
recursive, auth_token_ttl) {}
etcd::Watcher::Watcher(std::string const & address, etcd::Watcher::Watcher(std::string const& address, std::string const& username,
std::string const & username, std::string const & password, std::string const& password, std::string const& key,
std::string const & key, std::string const & range_end, std::string const& range_end,
std::function<void(Response)> callback): std::function<void(Response)> callback,
Watcher(address, username, password, key, range_end, -1, callback) { std::function<void(bool)> wait_callback,
} int const auth_token_ttl)
: Watcher(address, username, password, key, range_end, -1, callback,
wait_callback, auth_token_ttl) {}
etcd::Watcher::Watcher(std::string const & address, etcd::Watcher::Watcher(std::string const& address, std::string const& username,
std::string const & username, std::string const & password, std::string const& password, std::string const& key,
std::string const & key, int fromIndex, int64_t fromIndex,
std::function<void(Response)> callback, bool recursive): std::function<void(Response)> callback,
Watcher(Client(address, username, password), key, fromIndex, callback, recursive) { std::function<void(bool)> wait_callback, bool recursive,
} int const auth_token_ttl)
: Watcher(SyncClient(address, username, password, auth_token_ttl), key,
fromIndex, callback, wait_callback, recursive) {}
etcd::Watcher::Watcher(std::string const & address, etcd::Watcher::Watcher(std::string const& address, std::string const& username,
std::string const & username, std::string const & password, std::string const& password, std::string const& key,
std::string const & key, std::string const & range_end, int fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback): std::function<void(Response)> callback,
Watcher(Client(address, username, password), key, range_end, fromIndex, callback) { std::function<void(bool)> wait_callback,
} int const auth_token_ttl)
: Watcher(SyncClient(address, username, password, auth_token_ttl), key,
range_end, fromIndex, callback, wait_callback) {}
etcd::Watcher::~Watcher() etcd::Watcher::Watcher(std::string const& address, std::string const& ca,
{ std::string const& cert, std::string const& privkey,
this->Cancel(); std::string const& key, int64_t fromIndex,
} std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, bool recursive,
std::string const& target_name_override)
: Watcher(SyncClient(address, ca, cert, privkey, target_name_override), key,
fromIndex, callback, wait_callback, recursive) {}
bool etcd::Watcher::Wait() etcd::Watcher::Watcher(std::string const& address, std::string const& ca,
{ std::string const& cert, std::string const& privkey,
std::string const& key, std::string const& range_end,
int64_t fromIndex,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback,
std::string const& target_name_override)
: Watcher(SyncClient(address, ca, cert, privkey, target_name_override), key,
range_end, fromIndex, callback, wait_callback) {}
etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::function<void(Response)> callback, bool recursive)
: Watcher(client, key, callback, nullptr, recursive) {}
etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::string const& range_end,
std::function<void(Response)> callback)
: Watcher(client, key, range_end, callback, nullptr) {}
etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
int64_t fromIndex,
std::function<void(Response)> callback, bool recursive)
: Watcher(client, key, fromIndex, callback, nullptr, recursive) {}
etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback)
: Watcher(client, key, range_end, fromIndex, callback, nullptr) {}
etcd::Watcher::Watcher(std::string const& address, std::string const& key,
std::function<void(Response)> callback, bool recursive)
: Watcher(address, key, callback, nullptr, recursive) {}
etcd::Watcher::Watcher(std::string const& address, std::string const& key,
std::string const& range_end,
std::function<void(Response)> callback)
: Watcher(address, key, range_end, callback, nullptr) {}
etcd::Watcher::Watcher(std::string const& address, std::string const& key,
int64_t fromIndex,
std::function<void(Response)> callback, bool recursive)
: Watcher(address, key, fromIndex, callback, nullptr, recursive) {}
etcd::Watcher::Watcher(std::string const& address, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback)
: Watcher(address, key, range_end, fromIndex, callback, nullptr) {}
etcd::Watcher::Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
std::function<void(Response)> callback, bool recursive,
int const auth_token_ttl)
: Watcher(address, username, password, key, callback, nullptr, recursive,
auth_token_ttl) {}
etcd::Watcher::Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
std::string const& range_end,
std::function<void(Response)> callback,
int const auth_token_ttl)
: Watcher(address, username, password, key, range_end, callback, nullptr,
auth_token_ttl) {}
etcd::Watcher::Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
int64_t fromIndex,
std::function<void(Response)> callback, bool recursive,
int const auth_token_ttl)
: Watcher(address, username, password, key, fromIndex, callback, nullptr,
recursive, auth_token_ttl) {}
etcd::Watcher::Watcher(std::string const& address, std::string const& username,
std::string const& password, std::string const& key,
std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback,
int const auth_token_ttl)
: Watcher(address, username, password, key, range_end, fromIndex, callback,
nullptr, auth_token_ttl) {}
etcd::Watcher::Watcher(std::string const& address, std::string const& ca,
std::string const& cert, std::string const& privkey,
std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, bool recursive,
std::string const& target_name_override)
: Watcher(address, ca, cert, privkey, key, fromIndex, callback, nullptr,
recursive, target_name_override) {}
etcd::Watcher::Watcher(std::string const& address, std::string const& ca,
std::string const& cert, std::string const& privkey,
std::string const& key, std::string const& range_end,
int64_t fromIndex,
std::function<void(Response)> callback,
std::string const& target_name_override)
: Watcher(address, ca, cert, privkey, key, range_end, fromIndex, callback,
nullptr, target_name_override) {}
etcd::Watcher::~Watcher() { this->Cancel(); }
bool etcd::Watcher::Wait() {
if (!cancelled.exchange(true)) { if (!cancelled.exchange(true)) {
if (task_.joinable()) { if (task_.joinable()) {
task_.join(); task_.join();
@ -105,28 +229,37 @@ bool etcd::Watcher::Wait()
return stubs->call->Cancelled(); return stubs->call->Cancelled();
} }
void etcd::Watcher::Wait(std::function<void(bool)> callback) bool etcd::Watcher::Wait(std::function<void(bool)> callback) {
{
if (wait_callback == nullptr) { if (wait_callback == nullptr) {
wait_callback = callback; wait_callback = callback;
return true;
} else { } else {
std::cerr << "Failed to set a asynchronous wait callback since it has already been set" << std::endl; #ifndef NDEBUG
std::cerr
<< "[warn] failed to set a asynchronous wait callback since it has "
"already been set"
<< std::endl;
#endif
return false;
} }
} }
void etcd::Watcher::Cancel() bool etcd::Watcher::Cancel() {
{
stubs->call->CancelWatch(); stubs->call->CancelWatch();
this->Wait(); return this->Wait();
} }
void etcd::Watcher::doWatch(std::string const & key, bool etcd::Watcher::Cancelled() const {
std::string const & range_end, return cancelled.load() || stubs->call->Cancelled();
std::string const & auth_token, }
std::function<void(Response)> callback)
{ void etcd::Watcher::doWatch(std::string const& key,
std::string const& range_end,
std::string const& auth_token,
std::function<void(Response)> callback) {
etcdv3::ActionParameters params; etcdv3::ActionParameters params;
params.auth_token.assign(auth_token); params.auth_token.assign(auth_token);
// n.b.: watch: no need for timeout
params.key.assign(key); params.key.assign(key);
params.range_end.assign(range_end); params.range_end.assign(range_end);
if (fromIndex >= 0) { if (fromIndex >= 0) {
@ -135,12 +268,19 @@ void etcd::Watcher::doWatch(std::string const & key,
params.withPrefix = recursive; params.withPrefix = recursive;
params.watch_stub = stubs->watchServiceStub.get(); params.watch_stub = stubs->watchServiceStub.get();
stubs->call.reset(new etcdv3::AsyncWatchAction(params)); stubs->call.reset(new etcdv3::AsyncWatchAction(std::move(params)));
task_ = std::thread([this, callback]() { task_ = std::thread([this, callback]() {
stubs->call->waitForResponse(callback); stubs->call->waitForResponse(callback);
if (wait_callback != nullptr) { if (wait_callback != nullptr) {
wait_callback(stubs->call->Cancelled()); // issue the callback in another thread (detached) to avoid deadlock,
// it is ok to detach a pplx::task, but we don't want to use the
// pplx::task in the core library
bool cancelled = stubs->call->Cancelled();
std::function<void(bool)> wait_callback = this->wait_callback;
std::thread canceller(
[wait_callback, cancelled]() { wait_callback(cancelled); });
canceller.detach();
} }
}); });
cancelled.store(false); cancelled.store(false);

View File

@ -1,9 +1,35 @@
#include <grpc/support/log.h>
#include "etcd/v3/Action.hpp" #include "etcd/v3/Action.hpp"
#include <grpc/support/log.h>
#include <grpcpp/support/status.h>
#include "etcd/v3/action_constants.hpp"
#include <cstdlib>
etcdv3::Action::Action(etcdv3::ActionParameters params) #ifndef GPR_ASSERT
{ #define GPR_ASSERT(x) \
if (!(x)) { \
fprintf(stderr, "%s:%d assert failed\n", __FILE__, __LINE__); \
abort(); \
}
#endif
etcdv3::Action::Action(etcdv3::ActionParameters const& params) {
parameters = params; parameters = params;
this->InitAction();
}
etcdv3::Action::Action(etcdv3::ActionParameters&& params) {
parameters = std::move(params);
this->InitAction();
}
etcdv3::Action::~Action() {
cq_.Shutdown();
// cancel on-the-fly calls
context.TryCancel();
}
void etcdv3::Action::InitAction() {
if (!parameters.auth_token.empty()) { if (!parameters.auth_token.empty()) {
// use `token` as the key, see: // use `token` as the key, see:
// //
@ -13,44 +39,97 @@ etcdv3::Action::Action(etcdv3::ActionParameters params)
start_timepoint = std::chrono::high_resolution_clock::now(); start_timepoint = std::chrono::high_resolution_clock::now();
} }
etcdv3::ActionParameters::ActionParameters() etcdv3::ActionParameters::ActionParameters() {
{
withPrefix = false; withPrefix = false;
revision = 0; revision = 0;
old_revision = 0; old_revision = 0;
lease_id = 0; lease_id = 0;
ttl = 0; ttl = 0;
keys_only = false;
count_only = false;
kv_stub = NULL; kv_stub = NULL;
watch_stub = NULL; watch_stub = NULL;
lease_stub = NULL; lease_stub = NULL;
} }
void etcdv3::Action::waitForResponse() bool etcdv3::ActionParameters::has_grpc_timeout() const {
{ return this->grpc_timeout != std::chrono::microseconds::zero();
void* got_tag;
bool ok = false;
cq_.Next(&got_tag, &ok);
GPR_ASSERT(got_tag == (void*)this);
} }
const std::chrono::high_resolution_clock::time_point etcdv3::Action::startTimepoint() { std::chrono::system_clock::time_point etcdv3::ActionParameters::grpc_deadline()
const {
return std::chrono::system_clock::now() + this->grpc_timeout;
}
void etcdv3::ActionParameters::dump(std::ostream& os) const {
os << "ActionParameters:" << std::endl;
os << " withPrefix: " << withPrefix << std::endl;
os << " revision: " << revision << std::endl;
os << " old_revision: " << old_revision << std::endl;
os << " lease_id: " << lease_id << std::endl;
os << " ttl: " << ttl << std::endl;
os << " limit: " << limit << std::endl;
os << " name: " << name << std::endl;
os << " key: " << key << std::endl;
os << " range_end: " << range_end << std::endl;
os << " keys_only: " << keys_only << std::endl;
os << " count_only: " << count_only << std::endl;
os << " value: " << value << std::endl;
os << " old_value: " << old_value << std::endl;
os << " auth_token: " << auth_token << std::endl;
os << " grpc_timeout: " << grpc_timeout.count() << "(ms)" << std::endl;
}
void etcdv3::Action::waitForResponse() {
void* got_tag;
bool ok = false;
if (parameters.has_grpc_timeout()) {
switch (cq_.AsyncNext(&got_tag, &ok, parameters.grpc_deadline())) {
case CompletionQueue::NextStatus::TIMEOUT: {
status =
grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, "gRPC timeout");
break;
}
case CompletionQueue::NextStatus::SHUTDOWN: {
status =
grpc::Status(grpc::StatusCode::UNAVAILABLE, "gRPC already shutdown");
break;
}
case CompletionQueue::NextStatus::GOT_EVENT: {
if (!ok) {
status =
grpc::Status(grpc::StatusCode::ABORTED,
"Failed to execute the action: not ok or invalid tag");
}
break;
}
}
} else {
cq_.Next(&got_tag, &ok);
GPR_ASSERT(got_tag == (void*) this);
}
}
const std::chrono::high_resolution_clock::time_point
etcdv3::Action::startTimepoint() {
return this->start_timepoint; return this->start_timepoint;
} }
std::string etcdv3::detail::string_plus_one(std::string const &value) { std::string etcdv3::detail::string_plus_one(std::string const& value) {
// referred from the Go implementation in etcd. // Referred from the Go implementation in etcd.
char *s = static_cast<char *>(calloc(value.size() + 1, sizeof(char))); for (int32_t i = value.size() - 1; i >= 0; --i) {
std::memcpy(s, value.c_str(), value.size()); if (static_cast<unsigned char>(value[i]) < 0xff) {
for (int i = value.size() - 1; i >= 0; --i) { std::string s = value.substr(0, i + 1);
if (static_cast<unsigned char>(s[i]) < 0xff) {
s[i] = s[i] + 1; s[i] = s[i] + 1;
std::string ret = std::string(s, i + 1); return s;
free(s);
return ret;
} }
} }
// see: noPrefixEnd in etcd, however c++ doesn't allows '\0' inside a string, thus we use return {etcdv3::NUL};
// the UTF-8 char U+0000 (i.e., "\xC0\x80"). }
return {"\xC0\x80"};
std::string etcdv3::detail::resolve_etcd_endpoints(
std::string const& default_endpoints) {
const char* ep = std::getenv("ETCD_ENDPOINTS");
return ep ? ep : default_endpoints;
} }

View File

@ -1,55 +0,0 @@
#include "etcd/v3/AsyncCompareAndDeleteAction.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Transaction.hpp"
using etcdserverpb::Compare;
using etcdserverpb::RangeRequest;
using etcdserverpb::PutRequest;
using etcdserverpb::RequestOp;
using etcdserverpb::ResponseOp;
using etcdserverpb::TxnRequest;
etcdv3::AsyncCompareAndDeleteAction::AsyncCompareAndDeleteAction(etcdv3::ActionParameters param, etcdv3::Atomicity_Type type)
:etcdv3::Action(param)
{
etcdv3::Transaction transaction(parameters.key);
if(type == etcdv3::Atomicity_Type::PREV_VALUE)
{
transaction.init_compare(parameters.old_value, Compare::CompareResult::Compare_CompareResult_EQUAL,
Compare::CompareTarget::Compare_CompareTarget_VALUE);
}
else if (type == etcdv3::Atomicity_Type::PREV_INDEX)
{
transaction.init_compare(parameters.old_revision, Compare::CompareResult::Compare_CompareResult_EQUAL,
Compare::CompareTarget::Compare_CompareTarget_MOD);
}
transaction.setup_compare_and_delete_operation(parameters.key);
transaction.setup_basic_failure_operation(parameters.key);
response_reader = parameters.kv_stub->AsyncTxn(&context, *transaction.txn_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncTxnResponse etcdv3::AsyncCompareAndDeleteAction::ParseResponse()
{
AsyncTxnResponse txn_resp;
if(!status.ok())
{
txn_resp.set_error_code(status.error_code());
txn_resp.set_error_message(status.error_message());
}
else
{
txn_resp.ParseResponse(parameters.key, parameters.withPrefix, reply);
txn_resp.set_action(etcdv3::COMPAREDELETE_ACTION);
if(!reply.succeeded())
{
txn_resp.set_error_code(101);
txn_resp.set_error_message("Compare failed");
}
}
return txn_resp;
}

View File

@ -1,58 +0,0 @@
#include "etcd/v3/AsyncCompareAndSwapAction.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Transaction.hpp"
using etcdserverpb::Compare;
using etcdserverpb::RangeRequest;
using etcdserverpb::PutRequest;
using etcdserverpb::RequestOp;
using etcdserverpb::ResponseOp;
using etcdserverpb::TxnRequest;
etcdv3::AsyncCompareAndSwapAction::AsyncCompareAndSwapAction(etcdv3::ActionParameters param, etcdv3::Atomicity_Type type)
: etcdv3::Action(param)
{
etcdv3::Transaction transaction(parameters.key);
if(type == etcdv3::Atomicity_Type::PREV_VALUE)
{
transaction.init_compare(parameters.old_value, Compare::CompareResult::Compare_CompareResult_EQUAL,
Compare::CompareTarget::Compare_CompareTarget_VALUE);
}
else if (type == etcdv3::Atomicity_Type::PREV_INDEX)
{
transaction.init_compare(parameters.old_revision, Compare::CompareResult::Compare_CompareResult_EQUAL,
Compare::CompareTarget::Compare_CompareTarget_MOD);
}
transaction.setup_basic_failure_operation(parameters.key);
transaction.setup_compare_and_swap_sequence(parameters.value, parameters.lease_id);
response_reader = parameters.kv_stub->AsyncTxn(&context, *transaction.txn_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncTxnResponse etcdv3::AsyncCompareAndSwapAction::ParseResponse()
{
AsyncTxnResponse txn_resp;
if(!status.ok())
{
txn_resp.set_error_code(status.error_code());
txn_resp.set_error_message(status.error_message());
}
else
{
txn_resp.ParseResponse(parameters.key, parameters.withPrefix, reply);
txn_resp.set_action(etcdv3::COMPARESWAP_ACTION);
//if there is an error code returned by parseResponse, we must
//not overwrite it.
if(!reply.succeeded() && !txn_resp.get_error_code())
{
txn_resp.set_error_code(101);
txn_resp.set_error_message("Compare failed");
}
}
return txn_resp;
}

View File

@ -1,43 +0,0 @@
#include "etcd/v3/AsyncDeleteAction.hpp"
#include "etcd/v3/action_constants.hpp"
using etcdserverpb::DeleteRangeRequest;
etcdv3::AsyncDeleteAction::AsyncDeleteAction(ActionParameters param)
: etcdv3::Action(param)
{
DeleteRangeRequest del_request;
del_request.set_key(parameters.key);
del_request.set_prev_kv(true);
if(parameters.withPrefix)
{
if (parameters.key.empty()) {
del_request.set_range_end(detail::string_plus_one("\0"));
} else {
del_request.set_range_end(detail::string_plus_one(parameters.key));
}
}
if(!parameters.range_end.empty()) {
del_request.set_range_end(parameters.range_end);
}
response_reader = parameters.kv_stub->AsyncDeleteRange(&context, del_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncDeleteRangeResponse etcdv3::AsyncDeleteAction::ParseResponse()
{
AsyncDeleteRangeResponse del_resp;
if(!status.ok())
{
del_resp.set_error_code(status.error_code());
del_resp.set_error_message(status.error_message());
}
else
{
del_resp.ParseResponse(parameters.key, parameters.withPrefix || !parameters.range_end.empty(), reply);
}
return del_resp;
}

View File

@ -1,34 +0,0 @@
#include "etcd/v3/AsyncDeleteRangeResponse.hpp"
#include "etcd/v3/action_constants.hpp"
void etcdv3::AsyncDeleteRangeResponse::ParseResponse(std::string const& key, bool prefix, DeleteRangeResponse& resp)
{
index = resp.header().revision();
if(resp.prev_kvs_size() == 0)
{
error_code=100;
error_message="Key not found";
}
else
{
action = etcdv3::DELETE_ACTION;
//get all previous values
for(int cnt=0; cnt < resp.prev_kvs_size(); cnt++)
{
etcdv3::KeyValue kv;
kv.kvs.CopyFrom(resp.prev_kvs(cnt));
values.push_back(kv);
}
if(!prefix)
{
prev_value = values[0];
value = values[0];
value.kvs.clear_value();
values.clear();
}
}
}

1496
src/v3/AsyncGRPC.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +0,0 @@
#include "etcd/v3/AsyncGetAction.hpp"
#include <cstdlib>
#include "etcd/v3/action_constants.hpp"
using etcdserverpb::RangeRequest;
etcdv3::AsyncGetAction::AsyncGetAction(etcdv3::ActionParameters param)
: etcdv3::Action(param)
{
RangeRequest get_request;
if (parameters.key.empty()) {
get_request.set_key("\0");
} else {
get_request.set_key(parameters.key);
}
get_request.set_limit(param.limit);
if(parameters.withPrefix)
{
if (parameters.key.empty()) {
get_request.set_range_end(detail::string_plus_one("\0"));
} else {
get_request.set_range_end(detail::string_plus_one(parameters.key));
}
}
if(!parameters.range_end.empty()) {
get_request.set_range_end(parameters.range_end);
}
get_request.set_sort_order(RangeRequest::SortOrder::RangeRequest_SortOrder_NONE);
response_reader = parameters.kv_stub->AsyncRange(&context,get_request,&cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncRangeResponse etcdv3::AsyncGetAction::ParseResponse()
{
AsyncRangeResponse range_resp;
if(!status.ok())
{
range_resp.set_error_code(status.error_code());
range_resp.set_error_message(status.error_message());
}
else
{
range_resp.ParseResponse(reply, parameters.withPrefix || !parameters.range_end.empty());
}
return range_resp;
}

View File

@ -1,179 +0,0 @@
#include "etcd/v3/AsyncLeaseAction.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Transaction.hpp"
using etcdserverpb::LeaseGrantRequest;
using etcdserverpb::LeaseRevokeRequest;
using etcdserverpb::LeaseCheckpointRequest;
using etcdserverpb::LeaseKeepAliveRequest;
using etcdserverpb::LeaseTimeToLiveRequest;
using etcdserverpb::LeaseLeasesRequest;
etcdv3::AsyncLeaseGrantAction::AsyncLeaseGrantAction(etcdv3::ActionParameters param)
: etcdv3::Action(param)
{
LeaseGrantRequest leasegrant_request;
leasegrant_request.set_ttl(parameters.ttl);
// If ID is set to 0, etcd will choose an ID.
leasegrant_request.set_id(parameters.lease_id);
response_reader = parameters.lease_stub->AsyncLeaseGrant(&context, leasegrant_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncLeaseGrantResponse etcdv3::AsyncLeaseGrantAction::ParseResponse()
{
AsyncLeaseGrantResponse lease_resp;
if (!status.ok()) {
lease_resp.set_error_code(status.error_code());
lease_resp.set_error_message(status.error_message());
} else {
lease_resp.ParseResponse(reply);
}
return lease_resp;
}
etcdv3::AsyncLeaseRevokeAction::AsyncLeaseRevokeAction(etcdv3::ActionParameters param)
: etcdv3::Action(param)
{
LeaseRevokeRequest leaserevoke_request;
leaserevoke_request.set_id(parameters.lease_id);
response_reader = parameters.lease_stub->AsyncLeaseRevoke(&context, leaserevoke_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncLeaseRevokeResponse etcdv3::AsyncLeaseRevokeAction::ParseResponse()
{
AsyncLeaseRevokeResponse lease_resp;
if (!status.ok()) {
lease_resp.set_error_code(status.error_code());
lease_resp.set_error_message(status.error_message());
} else {
lease_resp.ParseResponse(reply);
}
return lease_resp;
}
etcdv3::AsyncLeaseKeepAliveAction::AsyncLeaseKeepAliveAction(etcdv3::ActionParameters param)
: etcdv3::Action(param)
{
isCancelled = false;
stream = parameters.lease_stub->AsyncLeaseKeepAlive(&context, &cq_, (void*)"keepalive create");
void *got_tag = nullptr;
bool ok = false;
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)"keepalive create") {
// ok
} else {
throw std::runtime_error("Failed to create a lease keep-alive connection");
}
}
etcdv3::AsyncLeaseKeepAliveResponse etcdv3::AsyncLeaseKeepAliveAction::ParseResponse()
{
AsyncLeaseKeepAliveResponse lease_resp;
if (!status.ok()) {
lease_resp.set_error_code(status.error_code());
lease_resp.set_error_message(status.error_message());
} else {
lease_resp.ParseResponse(reply);
}
return lease_resp;
}
etcd::Response etcdv3::AsyncLeaseKeepAliveAction::Refresh()
{
std::lock_guard<std::mutex> scope_lock(this->protect_is_cancelled);
auto start_timepoint = std::chrono::high_resolution_clock::now();
if (isCancelled) {
auto resp = ParseResponse();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now() - start_timepoint);
return etcd::Response(resp, duration);
}
LeaseKeepAliveRequest leasekeepalive_request;
leasekeepalive_request.set_id(parameters.lease_id);
void *got_tag = nullptr;
bool ok = false;
stream->Write(leasekeepalive_request, (void *)"keepalive write");
// wait write finish
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)"keepalive write") {
stream->Read(&reply, (void*)"keepalive read");
// wait read finish
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)"keepalive read") {
auto resp = ParseResponse();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now() - start_timepoint);
return etcd::Response(resp, duration);
}
}
return etcd::Response(grpc::StatusCode::ABORTED, "Failed to create a lease keep-alive connection");
}
void etcdv3::AsyncLeaseKeepAliveAction::CancelKeepAlive()
{
std::lock_guard<std::mutex> scope_lock(this->protect_is_cancelled);
if(isCancelled == false)
{
isCancelled = true;
stream->WritesDone((void*)"keepalive done");
grpc::Status status;
stream->Finish(&status, (void *)this);
cq_.Shutdown();
}
}
bool etcdv3::AsyncLeaseKeepAliveAction::Cancelled() const
{
return isCancelled;
}
etcdv3::AsyncLeaseTimeToLiveAction::AsyncLeaseTimeToLiveAction(etcdv3::ActionParameters param)
: etcdv3::Action(param)
{
LeaseTimeToLiveRequest leasetimetolive_request;
leasetimetolive_request.set_id(parameters.lease_id);
// FIXME: unsupported parameters: "keys"
// leasetimetolive_request.set_keys(parameters.keys);
response_reader = parameters.lease_stub->AsyncLeaseTimeToLive(&context, leasetimetolive_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncLeaseTimeToLiveResponse etcdv3::AsyncLeaseTimeToLiveAction::ParseResponse()
{
AsyncLeaseTimeToLiveResponse lease_resp;
if (!status.ok()) {
lease_resp.set_error_code(status.error_code());
lease_resp.set_error_message(status.error_message());
} else {
lease_resp.ParseResponse(reply);
}
return lease_resp;
}
etcdv3::AsyncLeaseLeasesAction::AsyncLeaseLeasesAction(etcdv3::ActionParameters param)
: etcdv3::Action(param)
{
LeaseLeasesRequest leaseleases_request;
response_reader = parameters.lease_stub->AsyncLeaseLeases(&context, leaseleases_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncLeaseLeasesResponse etcdv3::AsyncLeaseLeasesAction::ParseResponse()
{
AsyncLeaseLeasesResponse lease_resp;
if (!status.ok()) {
lease_resp.set_error_code(status.error_code());
lease_resp.set_error_message(status.error_message());
} else {
lease_resp.ParseResponse(reply);
}
return lease_resp;
}

View File

@ -1,35 +0,0 @@
#include "etcd/v3/AsyncLeaseResponse.hpp"
#include "etcd/v3/action_constants.hpp"
void etcdv3::AsyncLeaseGrantResponse::ParseResponse(LeaseGrantResponse& resp) {
index = resp.header().revision();
value.kvs.set_lease(resp.id());
value.set_ttl(resp.ttl());
error_message = resp.error();
}
void etcdv3::AsyncLeaseRevokeResponse::ParseResponse(LeaseRevokeResponse& resp) {
index = resp.header().revision();
}
void etcdv3::AsyncLeaseKeepAliveResponse::ParseResponse(LeaseKeepAliveResponse& resp) {
index = resp.header().revision();
value.kvs.set_lease(resp.id());
value.set_ttl(resp.ttl());
}
void etcdv3::AsyncLeaseTimeToLiveResponse::ParseResponse(LeaseTimeToLiveResponse& resp) {
index = resp.header().revision();
value.kvs.set_lease(resp.id());
value.set_ttl(resp.ttl());
// FIXME: unsupported: fields "grantedTTL" and "keys"
}
void etcdv3::AsyncLeaseLeasesResponse::ParseResponse(LeaseLeasesResponse& resp) {
index = resp.header().revision();
// FIXME: only the first leases is recorded.
if (resp.leases_size() > 0) {
value.kvs.set_lease(resp.leases(0).id());
}
}

View File

@ -1,62 +0,0 @@
#include "etcd/v3/AsyncLockAction.hpp"
#include "etcd/v3/action_constants.hpp"
using v3lockpb::LockRequest;
using v3lockpb::UnlockRequest;
etcdv3::AsyncLockAction::AsyncLockAction(ActionParameters param)
: etcdv3::Action(param)
{
LockRequest lock_request;
lock_request.set_name(parameters.key);
lock_request.set_lease(parameters.lease_id);
response_reader = parameters.lock_stub->AsyncLock(&context, lock_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncLockResponse etcdv3::AsyncLockAction::ParseResponse()
{
AsyncLockResponse lock_resp;
if(!status.ok())
{
lock_resp.set_error_code(status.error_code());
lock_resp.set_error_message(status.error_message());
}
else
{
lock_resp.ParseResponse(reply);
lock_resp.set_action(etcdv3::LOCK_ACTION);
}
return lock_resp;
}
etcdv3::AsyncUnlockAction::AsyncUnlockAction(ActionParameters param)
: etcdv3::Action(param)
{
UnlockRequest unlock_request;
unlock_request.set_key(parameters.key);
response_reader = parameters.lock_stub->AsyncUnlock(&context, unlock_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncUnlockResponse etcdv3::AsyncUnlockAction::ParseResponse()
{
AsyncUnlockResponse unlock_resp;
if(!status.ok())
{
unlock_resp.set_error_code(status.error_code());
unlock_resp.set_error_message(status.error_message());
}
else
{
unlock_resp.ParseResponse(reply);
unlock_resp.set_action(etcdv3::UNLOCK_ACTION);
}
return unlock_resp;
}

View File

@ -1,14 +0,0 @@
#include "etcd/v3/AsyncLockResponse.hpp"
#include "etcd/v3/action_constants.hpp"
void etcdv3::AsyncLockResponse::ParseResponse(LockResponse& resp)
{
index = resp.header().revision();
lock_key = resp.key();
}
void etcdv3::AsyncUnlockResponse::ParseResponse(UnlockResponse& resp)
{
index = resp.header().revision();
}

View File

@ -1,30 +0,0 @@
#include "etcd/v3/AsyncRangeResponse.hpp"
#include "etcd/v3/action_constants.hpp"
void etcdv3::AsyncRangeResponse::ParseResponse(RangeResponse& resp, bool prefix)
{
action = etcdv3::GET_ACTION;
index = resp.header().revision();
if(resp.kvs_size() == 0 && !prefix)
{
error_code=100;
error_message="Key not found";
return;
}
else
{
for(int index=0; index < resp.kvs_size(); index++)
{
etcdv3::KeyValue kv;
kv.kvs.CopyFrom(resp.kvs(index));
values.push_back(kv);
}
if(!prefix)
{
value = values[0];
values.clear();
}
}
}

View File

@ -1,52 +0,0 @@
#include "etcd/v3/AsyncSetAction.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Transaction.hpp"
using etcdserverpb::Compare;
etcdv3::AsyncSetAction::AsyncSetAction(etcdv3::ActionParameters param, bool create)
: etcdv3::Action(param)
{
etcdv3::Transaction transaction(parameters.key);
isCreate = create;
transaction.init_compare(Compare::CompareResult::Compare_CompareResult_EQUAL,
Compare::CompareTarget::Compare_CompareTarget_VERSION);
transaction.setup_basic_create_sequence(parameters.key, parameters.value, parameters.lease_id);
if(isCreate)
{
transaction.setup_basic_failure_operation(parameters.key);
}
else
{
transaction.setup_set_failure_operation(parameters.key, parameters.value, parameters.lease_id);
}
response_reader = parameters.kv_stub->AsyncTxn(&context, *transaction.txn_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncTxnResponse etcdv3::AsyncSetAction::ParseResponse()
{
AsyncTxnResponse txn_resp;
if(!status.ok())
{
txn_resp.set_error_code(status.error_code());
txn_resp.set_error_message(status.error_message());
}
else
{
txn_resp.ParseResponse(parameters.key, parameters.withPrefix, reply);
std::string action = isCreate? etcdv3::CREATE_ACTION:etcdv3::SET_ACTION;
txn_resp.set_action(action);
if(!reply.succeeded() && action == etcdv3::CREATE_ACTION)
{
txn_resp.set_error_code(105);
txn_resp.set_error_message("Key already exists");
}
}
return txn_resp;
}

View File

@ -1,37 +0,0 @@
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/AsyncTxnAction.hpp"
#include "etcd/v3/Transaction.hpp"
etcdv3::AsyncTxnAction::AsyncTxnAction(etcdv3::ActionParameters param, etcdv3::Transaction const &tx)
: etcdv3::Action(param)
{
response_reader = parameters.kv_stub->AsyncTxn(&context, *tx.txn_request, &cq_);
response_reader->Finish(&reply, &status, (void *)this);
}
etcdv3::AsyncTxnResponse etcdv3::AsyncTxnAction::ParseResponse()
{
AsyncTxnResponse txn_resp;
if(!status.ok())
{
txn_resp.set_error_code(status.error_code());
txn_resp.set_error_message(status.error_message());
}
else
{
txn_resp.ParseResponse(parameters.key, parameters.withPrefix, reply);
txn_resp.set_action(etcdv3::TXN_ACTION);
//if there is an error code returned by parseResponse, we must
//not overwrite it.
if(!reply.succeeded() && !txn_resp.get_error_code())
{
txn_resp.set_error_code(101);
txn_resp.set_error_message("compare failed");
}
}
return txn_resp;
}

View File

@ -1,48 +0,0 @@
#include "etcd/v3/AsyncTxnResponse.hpp"
#include "etcd/v3/AsyncRangeResponse.hpp"
#include "etcd/v3/AsyncDeleteRangeResponse.hpp"
#include "etcd/v3/action_constants.hpp"
using etcdserverpb::ResponseOp;
void etcdv3::AsyncTxnResponse::ParseResponse(TxnResponse& reply) {
index = reply.header().revision();
}
void etcdv3::AsyncTxnResponse::ParseResponse(std::string const& key, bool prefix, TxnResponse& reply)
{
index = reply.header().revision();
for(int index=0; index < reply.responses_size(); index++)
{
auto resp = reply.responses(index);
if(ResponseOp::ResponseCase::kResponseRange == resp.response_case())
{
AsyncRangeResponse response;
response.ParseResponse(*(resp.mutable_response_range()),prefix);
error_code = response.get_error_code();
error_message = response.get_error_message();
values = response.get_values();
value = response.get_value();
}
else if(ResponseOp::ResponseCase::kResponsePut == resp.response_case())
{
auto put_resp = resp.response_put();
if(put_resp.has_prev_kv())
{
prev_value.kvs.CopyFrom(put_resp.prev_kv());
}
}
else if(ResponseOp::ResponseCase::kResponseDeleteRange == resp.response_case())
{
AsyncDeleteRangeResponse response;
response.ParseResponse(key,prefix,*(resp.mutable_response_delete_range()));
prev_value.kvs.CopyFrom(response.get_prev_value().kvs);
values = response.get_values();
value = response.get_value();
}
}
}

View File

@ -1,50 +0,0 @@
#include "etcd/v3/AsyncUpdateAction.hpp"
#include "etcd/v3/AsyncRangeResponse.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Transaction.hpp"
using etcdserverpb::Compare;
using etcdserverpb::RangeRequest;
using etcdserverpb::PutRequest;
using etcdserverpb::RequestOp;
using etcdserverpb::ResponseOp;
using etcdserverpb::TxnRequest;
etcdv3::AsyncUpdateAction::AsyncUpdateAction(etcdv3::ActionParameters param)
: etcdv3::Action(param)
{
etcdv3::Transaction transaction(parameters.key);
transaction.init_compare(Compare::CompareResult::Compare_CompareResult_GREATER,
Compare::CompareTarget::Compare_CompareTarget_VERSION);
transaction.setup_compare_and_swap_sequence(parameters.value, parameters.lease_id);
response_reader = parameters.kv_stub->AsyncTxn(&context, *transaction.txn_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncTxnResponse etcdv3::AsyncUpdateAction::ParseResponse()
{
AsyncTxnResponse txn_resp;
if(!status.ok())
{
txn_resp.set_error_code(status.error_code());
txn_resp.set_error_message(status.error_message());
}
else
{
if(reply.succeeded())
{
txn_resp.ParseResponse(parameters.key, parameters.withPrefix, reply);
txn_resp.set_action(etcdv3::UPDATE_ACTION);
}
else
{
txn_resp.set_error_code(100);
txn_resp.set_error_message("Key not found");
}
}
return txn_resp;
}

View File

@ -1,171 +0,0 @@
#include "etcd/v3/AsyncWatchAction.hpp"
#include "etcd/v3/action_constants.hpp"
using etcdserverpb::RangeRequest;
using etcdserverpb::RangeResponse;
using etcdserverpb::WatchCreateRequest;
etcdv3::AsyncWatchAction::AsyncWatchAction(etcdv3::ActionParameters param)
: etcdv3::Action(param)
{
isCancelled.store(false);
stream = parameters.watch_stub->AsyncWatch(&context,&cq_,(void*)"create");
WatchRequest watch_req;
WatchCreateRequest watch_create_req;
watch_create_req.set_key(parameters.key);
watch_create_req.set_prev_kv(true);
watch_create_req.set_start_revision(parameters.revision);
if(parameters.withPrefix)
{
if (parameters.key.empty()) {
watch_create_req.set_range_end(detail::string_plus_one("\0"));
} else {
watch_create_req.set_range_end(detail::string_plus_one(parameters.key));
}
}
if(!parameters.range_end.empty()) {
watch_create_req.set_range_end(parameters.range_end);
}
watch_req.mutable_create_request()->CopyFrom(watch_create_req);
// wait "create" success (the stream becomes ready)
void *got_tag;
bool ok = false;
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)"create") {
stream->Write(watch_req, (void *)"write");
} else {
throw std::runtime_error("failed to create a watch connection");
}
// wait "write" (WatchCreateRequest) success, and start to read the first reply
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)"write") {
stream->Read(&reply, (void*)this);
} else {
throw std::runtime_error("failed to write WatchCreateRequest to server");
}
}
void etcdv3::AsyncWatchAction::waitForResponse()
{
void* got_tag;
bool ok = false;
while(cq_.Next(&got_tag, &ok))
{
if(ok == false)
{
break;
}
if(got_tag == (void*)"writes done") {
isCancelled.store(true);
cq_.Shutdown();
break;
}
if(got_tag == (void*)this) // read tag
{
if (reply.canceled()) {
isCancelled.store(true);
cq_.Shutdown();
}
else if ((reply.created() && reply.header().revision() < parameters.revision) ||
reply.events_size() > 0) {
// we stop watch under two conditions:
//
// 1. watch for a future revision, return immediately with empty events set
// 2. receive any effective events.
isCancelled.store(true);
stream->WritesDone((void*)"writes done");
grpc::Status status;
stream->Finish(&status, (void *)this);
cq_.Shutdown();
// leave a warning if the response is too large and been fragmented
if (reply.fragment()) {
std::cerr << "WARN: The response hasn't been fully received and parsed" << std::endl;
}
}
else
{
// otherwise, start next round read-reply
stream->Read(&reply, (void*)this);
}
}
}
}
void etcdv3::AsyncWatchAction::CancelWatch()
{
std::lock_guard<std::mutex> scope_lock(this->protect_is_cancalled);
if (!isCancelled.exchange(true)) {
stream->WritesDone((void*)"writes done");
grpc::Status status;
stream->Finish(&status, (void *)this);
cq_.Shutdown();
}
}
bool etcdv3::AsyncWatchAction::Cancelled() const {
return isCancelled.load();
}
void etcdv3::AsyncWatchAction::waitForResponse(std::function<void(etcd::Response)> callback)
{
void* got_tag;
bool ok = false;
while(cq_.Next(&got_tag, &ok))
{
if(ok == false)
{
break;
}
if(got_tag == (void*)"writes done")
{
isCancelled.store(true);
cq_.Shutdown();
break;
}
else if(got_tag == (void*)this) // read tag
{
if (reply.canceled()) {
isCancelled.store(true);
cq_.Shutdown();
if (reply.compact_revision() != 0) {
callback(etcd::Response(grpc::StatusCode::OUT_OF_RANGE /* error code */,
"required revision has been compacted"));
}
break;
}
if(reply.events_size())
{
// for the callback case, we don't stop immediately if watching for a future revison,
// we wait until there are some expected events.
auto resp = ParseResponse();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now() - start_timepoint);
callback(etcd::Response(resp, duration));
start_timepoint = std::chrono::high_resolution_clock::now();
}
stream->Read(&reply, (void*)this);
}
}
}
etcdv3::AsyncWatchResponse etcdv3::AsyncWatchAction::ParseResponse()
{
AsyncWatchResponse watch_resp;
if(!status.ok())
{
watch_resp.set_error_code(status.error_code());
watch_resp.set_error_message(status.error_message());
}
else
{
watch_resp.ParseResponse(reply);
}
return watch_resp;
}

View File

@ -1,45 +0,0 @@
#include "etcd/v3/AsyncWatchResponse.hpp"
#include "etcd/v3/action_constants.hpp"
void etcdv3::AsyncWatchResponse::ParseResponse(WatchResponse& reply)
{
if (reply.canceled() && reply.compact_revision() != 0) {
error_code=grpc::StatusCode::OUT_OF_RANGE;
error_message="required revision has been compacted";
return;
}
index = reply.header().revision();
for (auto const &e: reply.events()) {
events.emplace_back(e);
}
for(int cnt =0; cnt < reply.events_size(); cnt++)
{
auto event = reply.events(cnt);
if(mvccpb::Event::EventType::Event_EventType_PUT == event.type())
{
if(event.kv().version() == 1)
{
action = etcdv3::CREATE_ACTION;
}
else
{
action = etcdv3::SET_ACTION;
}
value.kvs = event.kv();
}
else if(mvccpb::Event::EventType::Event_EventType_DELETE_ == event.type())
{
action = etcdv3::DELETE_ACTION;
value.kvs = event.kv();
}
if(event.has_prev_kv())
{
prev_value.kvs = event.prev_kv();
}
// just store the first occurence of the key in values.
// this is done so tas client will not need to change their behaviour.
// break immediately
break;
}
}

View File

@ -1,16 +1,7 @@
#include "etcd/v3/KeyValue.hpp" #include "etcd/v3/KeyValue.hpp"
etcdv3::KeyValue::KeyValue() etcdv3::KeyValue::KeyValue() { ttl = 0; }
{
ttl = 0;
}
void etcdv3::KeyValue::set_ttl(int ttl) void etcdv3::KeyValue::set_ttl(int ttl) { this->ttl = ttl; }
{
this->ttl = ttl;
}
int etcdv3::KeyValue::get_ttl() const int etcdv3::KeyValue::get_ttl() const { return ttl; }
{
return ttl;
}

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

@ -0,0 +1,40 @@
#include "etcd/v3/Member.hpp"
etcdv3::Member::Member() {
id = 0;
name = "";
peerURLs = {};
clientURLs = {};
isLearner = false;
}
void etcdv3::Member::set_id(uint64_t const& id) { this->id = id; }
void etcdv3::Member::set_name(std::string const& name) { this->name = name; }
void etcdv3::Member::set_peerURLs(std::vector<std::string> const& peerURLs) {
this->peerURLs = peerURLs;
}
void etcdv3::Member::set_clientURLs(
std::vector<std::string> const& clientURLs) {
this->clientURLs = clientURLs;
}
void etcdv3::Member::set_learner(bool isLearner) {
this->isLearner = isLearner;
}
uint64_t const& etcdv3::Member::get_id() const { return id; }
std::string const& etcdv3::Member::get_name() const { return name; }
std::vector<std::string> const& etcdv3::Member::get_peerURLs() const {
return peerURLs;
}
std::vector<std::string> const& etcdv3::Member::get_clientURLs() const {
return clientURLs;
}
bool etcdv3::Member::get_learner() const { return isLearner; }

View File

@ -2,180 +2,359 @@
#include "proto/rpc.grpc.pb.h" #include "proto/rpc.grpc.pb.h"
using etcdserverpb::Compare; #include "etcd/v3/Action.hpp"
using etcdserverpb::RangeRequest;
using etcdserverpb::PutRequest; namespace etcdv3 {
using etcdserverpb::RequestOp;
using etcdserverpb::DeleteRangeRequest; namespace detail {
static etcdserverpb::Compare::CompareResult to_compare_result(CompareResult r) {
return static_cast<etcdserverpb::Compare::CompareResult>(static_cast<int>(r));
}
static etcdserverpb::Compare::CompareTarget to_compare_target(CompareTarget t) {
return static_cast<etcdserverpb::Compare::CompareTarget>(static_cast<int>(t));
}
} // namespace detail
} // namespace etcdv3
etcdv3::Transaction::Transaction() { etcdv3::Transaction::Transaction() {
txn_request.reset(new etcdserverpb::TxnRequest{}); txn_request.reset(new etcdserverpb::TxnRequest{});
} }
etcdv3::Transaction::Transaction(const std::string& key) : key(key) { etcdv3::Transaction::~Transaction() {}
txn_request.reset(new etcdserverpb::TxnRequest{});
void etcdv3::Transaction::add_compare(std::string const& key,
CompareTarget const& target,
CompareResult const& result,
Value const& target_value,
std::string const& range_end) {
switch (target) {
case CompareTarget::VERSION:
add_compare_version(key, result, target_value.version, range_end);
break;
case CompareTarget::CREATE:
add_compare_create(key, result, target_value.create_revision, range_end);
break;
case CompareTarget::MOD:
add_compare_mod(key, result, target_value.mod_revision, range_end);
break;
case CompareTarget::VALUE:
add_compare_value(key, result, target_value.value, range_end);
break;
case CompareTarget::LEASE:
add_compare_lease(key, result, target_value.lease, range_end);
break;
default:
// ignore invalid compare target
break;
}
} }
void etcdv3::Transaction::init_compare(Compare::CompareResult result, Compare::CompareTarget target){ void etcdv3::Transaction::add_compare_version(std::string const& key,
Compare* compare = txn_request->add_compare(); int64_t const& version,
compare->set_result(result); std::string const& range_end) {
compare->set_target(target); this->add_compare_version(key, CompareResult::EQUAL, version, range_end);
compare->set_key(key);
compare->set_version(0);
} }
void etcdv3::Transaction::init_compare(std::string const& old_value, Compare::CompareResult result, Compare::CompareTarget target){ void etcdv3::Transaction::add_compare_version(std::string const& key,
Compare* compare = txn_request->add_compare(); CompareResult const& result,
compare->set_result(result); int64_t const& version,
compare->set_target(target); std::string const& range_end) {
compare->set_key(key); auto compare = txn_request->add_compare();
compare->set_result(detail::to_compare_result(result));
compare->set_value(old_value); compare->set_target(detail::to_compare_target(CompareTarget::VERSION));
compare->set_key(key);
compare->set_version(version);
compare->set_range_end(range_end);
} }
void etcdv3::Transaction::init_compare(int old_index, Compare::CompareResult result, Compare::CompareTarget target){ void etcdv3::Transaction::add_compare_create(std::string const& key,
Compare* compare = txn_request->add_compare(); int64_t const& create_revision,
compare->set_result(result); std::string const& range_end) {
compare->set_target(target); this->add_compare_create(key, CompareResult::EQUAL, create_revision,
compare->set_key(key); range_end);
compare->set_mod_revision(old_index);
} }
/** void etcdv3::Transaction::add_compare_create(std::string const& key,
* get key on failure CompareResult const& result,
*/ int64_t const& create_revision,
void etcdv3::Transaction::setup_basic_failure_operation(std::string const& key) { std::string const& range_end) {
std::unique_ptr<RangeRequest> get_request(new RangeRequest()); auto compare = txn_request->add_compare();
get_request->set_key(key); compare->set_result(detail::to_compare_result(result));
RequestOp* req_failure = txn_request->add_failure(); compare->set_target(detail::to_compare_target(CompareTarget::CREATE));
req_failure->set_allocated_request_range(get_request.release()); compare->set_key(key);
compare->set_create_revision(create_revision);
compare->set_range_end(range_end);
} }
/** void etcdv3::Transaction::add_compare_mod(std::string const& key,
* get key on failure, get key before put, modify and then get updated key int64_t const& mod_revision,
*/ std::string const& range_end) {
void etcdv3::Transaction::setup_set_failure_operation(std::string const &key, std::string const &value, int64_t leaseid) { this->add_compare_mod(key, CompareResult::EQUAL, mod_revision, range_end);
std::unique_ptr<PutRequest> put_request(new PutRequest());
put_request->set_key(key);
put_request->set_value(value);
put_request->set_prev_kv(true);
put_request->set_lease(leaseid);
RequestOp* req_failure = txn_request->add_failure();
req_failure->set_allocated_request_put(put_request.release());
std::unique_ptr<RangeRequest> get_request(new RangeRequest());
get_request->set_key(key);
req_failure = txn_request->add_failure();
req_failure->set_allocated_request_range(get_request.release());
} }
/** void etcdv3::Transaction::add_compare_mod(std::string const& key,
* add key and then get new value of key CompareResult const& result,
*/ int64_t const& mod_revision,
void etcdv3::Transaction::setup_basic_create_sequence(std::string const& key, std::string const& value, int64_t leaseid) { std::string const& range_end) {
std::unique_ptr<PutRequest> put_request(new PutRequest()); auto compare = txn_request->add_compare();
put_request->set_key(key); compare->set_result(detail::to_compare_result(result));
put_request->set_value(value); compare->set_target(detail::to_compare_target(CompareTarget::MOD));
put_request->set_prev_kv(true); compare->set_key(key);
put_request->set_lease(leaseid); compare->set_mod_revision(mod_revision);
RequestOp* req_success = txn_request->add_success(); compare->set_range_end(range_end);
req_success->set_allocated_request_put(put_request.release());
std::unique_ptr<RangeRequest> get_request(new RangeRequest());
get_request->set_key(key);
req_success = txn_request->add_success();
req_success->set_allocated_request_range(get_request.release());
} }
/** void etcdv3::Transaction::add_compare_value(std::string const& key,
* get key value then modify and get new value std::string const& value,
*/ std::string const& range_end) {
void etcdv3::Transaction::setup_compare_and_swap_sequence(std::string const& value, int64_t leaseid) { this->add_compare_value(key, CompareResult::EQUAL, value, range_end);
std::unique_ptr<PutRequest> put_request(new PutRequest());
put_request->set_key(key);
put_request->set_value(value);
put_request->set_prev_kv(true);
put_request->set_lease(leaseid);
RequestOp* req_success = txn_request->add_success();
req_success->set_allocated_request_put(put_request.release());
std::unique_ptr<RangeRequest> get_request(new RangeRequest());
get_request->set_key(key);
req_success = txn_request->add_success();
req_success->set_allocated_request_range(get_request.release());
} }
/** void etcdv3::Transaction::add_compare_value(std::string const& key,
* get key, delete CompareResult const& result,
*/ std::string const& value,
void etcdv3::Transaction::setup_delete_sequence(std::string const &key, std::string const &range_end, bool recursive) { std::string const& range_end) {
std::unique_ptr<DeleteRangeRequest> del_request(new DeleteRangeRequest()); auto compare = txn_request->add_compare();
del_request->set_key(key); compare->set_result(detail::to_compare_result(result));
del_request->set_prev_kv(true); compare->set_target(detail::to_compare_target(CompareTarget::VALUE));
if(recursive) compare->set_key(key);
{ compare->set_value(value);
del_request->set_range_end(range_end); compare->set_range_end(range_end);
}
RequestOp* req_success = txn_request->add_success();
req_success->set_allocated_request_delete_range(del_request.release());
} }
/** void etcdv3::Transaction::add_compare_lease(std::string const& key,
* get key, delete int64_t const& lease,
*/ std::string const& range_end) {
void etcdv3::Transaction::setup_delete_failure_operation(std::string const &key, std::string const &range_end, bool recursive) { this->add_compare_lease(key, CompareResult::EQUAL, lease, range_end);
std::unique_ptr<RangeRequest> get_request(new RangeRequest());
std::unique_ptr<DeleteRangeRequest> del_request(new DeleteRangeRequest());
get_request.reset(new RangeRequest());
get_request->set_key(key);
if(recursive)
{
get_request->set_range_end(range_end);
get_request->set_sort_target(RangeRequest::SortTarget::RangeRequest_SortTarget_KEY);
get_request->set_sort_order(RangeRequest::SortOrder::RangeRequest_SortOrder_ASCEND);
}
RequestOp* req_failure = txn_request->add_failure();
req_failure->set_allocated_request_range(get_request.release());
del_request.reset(new DeleteRangeRequest());
del_request->set_key(key);
if(recursive)
{
del_request->set_range_end(range_end);
}
req_failure = txn_request->add_failure();
req_failure->set_allocated_request_delete_range(del_request.release());
} }
void etcdv3::Transaction::setup_compare_and_delete_operation(std::string const& key) { void etcdv3::Transaction::add_compare_lease(std::string const& key,
std::unique_ptr<DeleteRangeRequest> del_request(new DeleteRangeRequest()); CompareResult const& result,
del_request->set_key(key); int64_t const& lease,
del_request->set_prev_kv(true); std::string const& range_end) {
RequestOp* req_success = txn_request->add_success(); auto compare = txn_request->add_compare();
req_success->set_allocated_request_delete_range(del_request.release()); compare->set_result(detail::to_compare_result(result));
compare->set_target(detail::to_compare_target(CompareTarget::LEASE));
compare->set_key(key);
compare->set_lease(lease);
compare->set_range_end(range_end);
} }
void etcdv3::Transaction::setup_put(std::string const &key, std::string const &value) { void etcdv3::Transaction::add_success_range(std::string const& key,
std::unique_ptr<PutRequest> put_request(new PutRequest()); std::string const& range_end,
put_request->set_key(key); bool const recursive,
put_request->set_value(value); const int64_t limit) {
put_request->set_prev_kv(false); auto succ = txn_request->add_success();
RequestOp* req_success = txn_request->add_success(); auto get_request = succ->mutable_request_range();
req_success->set_allocated_request_put(put_request.release()); etcdv3::detail::make_request_with_ranges(*get_request, key, range_end,
recursive);
get_request->set_limit(limit);
} }
void etcdv3::Transaction::setup_delete(std::string const &key) { void etcdv3::Transaction::add_success_put(std::string const& key,
std::unique_ptr<DeleteRangeRequest> del_request(new DeleteRangeRequest()); std::string const& value,
del_request->set_key(key); int64_t const leaseid,
del_request->set_prev_kv(false); const bool prev_kv) {
auto succ = txn_request->add_success();
RequestOp* req_success = txn_request->add_success(); auto put_request = succ->mutable_request_put();
req_success->set_allocated_request_delete_range(del_request.release()); put_request->set_key(key);
put_request->set_value(value);
put_request->set_prev_kv(prev_kv);
put_request->set_lease(leaseid);
} }
etcdv3::Transaction::~Transaction() { void etcdv3::Transaction::add_success_delete(std::string const& key,
std::string const& range_end,
bool const recursive,
const bool prev_kv) {
auto succ = txn_request->add_success();
auto del_request = succ->mutable_request_delete_range();
etcdv3::detail::make_request_with_ranges(*del_request, key, range_end,
recursive);
del_request->set_prev_kv(prev_kv);
}
void etcdv3::Transaction::add_success_txn(
const std::shared_ptr<Transaction> txn) {
auto succ = txn_request->add_success();
auto txn_request = succ->mutable_request_txn();
txn_request->CopyFrom(*txn->txn_request);
}
void etcdv3::Transaction::add_failure_range(std::string const& key,
std::string const& range_end,
bool const recursive,
const int64_t limit) {
auto fail = txn_request->add_failure();
auto get_request = fail->mutable_request_range();
etcdv3::detail::make_request_with_ranges(*get_request, key, range_end,
recursive);
get_request->set_limit(limit);
}
void etcdv3::Transaction::add_failure_put(std::string const& key,
std::string const& value,
int64_t const leaseid,
const bool prev_kv) {
auto fail = txn_request->add_failure();
auto put_request = fail->mutable_request_put();
put_request->set_key(key);
put_request->set_value(value);
put_request->set_prev_kv(prev_kv);
put_request->set_lease(leaseid);
}
void etcdv3::Transaction::add_failure_delete(std::string const& key,
std::string const& range_end,
bool const recursive,
const bool prev_kv) {
auto fail = txn_request->add_failure();
auto del_request = fail->mutable_request_delete_range();
etcdv3::detail::make_request_with_ranges(*del_request, key, range_end,
recursive);
del_request->set_prev_kv(prev_kv);
}
void etcdv3::Transaction::add_failure_txn(
const std::shared_ptr<Transaction> txn) {
auto fail = txn_request->add_failure();
auto txn_request = fail->mutable_request_txn();
txn_request->CopyFrom(*txn->txn_request);
}
void etcdv3::Transaction::setup_compare_and_create(
std::string const& key, std::string const& prev_value,
std::string const& create_key, std::string const& value,
int64_t const leaseid) {
this->add_compare_value(key, CompareResult::EQUAL, prev_value);
this->add_success_put(create_key, value, leaseid);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_or_create(std::string const& key,
std::string const& prev_value,
std::string const& create_key,
std::string const& value,
int64_t const leaseid) {
this->add_compare_value(key, CompareResult::NOT_EQUAL, prev_value);
this->add_success_put(create_key, value, leaseid);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_and_swap(std::string const& key,
std::string const& prev_value,
std::string const& value,
int64_t const leaseid) {
this->add_compare_value(key, CompareResult::EQUAL, prev_value);
this->add_success_put(key, value, leaseid);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_or_swap(std::string const& key,
std::string const& prev_value,
std::string const& value,
int64_t const leaseid) {
this->add_compare_value(key, CompareResult::NOT_EQUAL, prev_value);
this->add_success_put(key, value, leaseid);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_and_delete(
std::string const& key, std::string const& prev_value,
std::string const& delete_key, std::string const& range_end,
const bool recursive) {
this->add_compare_value(key, CompareResult::EQUAL, prev_value);
this->add_success_delete(delete_key, range_end, recursive,
true /* for backwards compatibility */);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_or_delete(std::string const& key,
std::string const& prev_value,
std::string const& delete_key,
std::string const& range_end,
const bool recursive) {
this->add_compare_value(key, CompareResult::NOT_EQUAL, prev_value);
this->add_success_delete(delete_key, range_end, recursive,
true /* for backwards compatibility */);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_and_create(
std::string const& key, const int64_t prev_revision,
std::string const& create_key, std::string const& value,
int64_t const leaseid) {
this->add_compare_mod(key, CompareResult::EQUAL, prev_revision);
this->add_success_put(create_key, value, leaseid);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_or_create(std::string const& key,
const int64_t prev_revision,
std::string const& create_key,
std::string const& value,
int64_t const leaseid) {
this->add_compare_mod(key, CompareResult::NOT_EQUAL, prev_revision);
this->add_success_put(create_key, value, leaseid);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_and_swap(std::string const& key,
const int64_t prev_revision,
std::string const& value,
int64_t const leaseid) {
this->add_compare_mod(key, CompareResult::EQUAL, prev_revision);
this->add_success_put(key, value, leaseid);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_or_swap(std::string const& key,
const int64_t prev_revision,
std::string const& value,
int64_t const leaseid) {
this->add_compare_mod(key, CompareResult::NOT_EQUAL, prev_revision);
this->add_success_put(key, value, leaseid);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_and_delete(
std::string const& key, const int64_t prev_revision,
std::string const& delete_key, std::string const& range_end,
const bool recursive) {
this->add_compare_mod(key, CompareResult::EQUAL, prev_revision);
this->add_success_delete(delete_key, range_end, recursive,
true /* for backwards compatibility */);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_compare_or_delete(std::string const& key,
const int64_t prev_revision,
std::string const& delete_key,
std::string const& range_end,
const bool recursive) {
this->add_compare_mod(key, CompareResult::NOT_EQUAL, prev_revision);
this->add_success_delete(delete_key, range_end, recursive,
true /* for backwards compatibility */);
this->add_failure_range(key);
}
void etcdv3::Transaction::setup_put(std::string const& key,
std::string const& value) {
this->add_success_put(key, value);
}
void etcdv3::Transaction::setup_delete(std::string const& key) {
this->add_success_delete(key, "", false,
true /* for backwards compatibility */);
}
void etcdv3::Transaction::setup_delete(std::string const& key,
std::string const& range_end,
const bool recursive) {
this->add_success_delete(key, range_end, recursive,
true /* for backwards compatibility */);
} }

View File

@ -1,74 +1,86 @@
#include "etcd/v3/V3Response.hpp" #include "etcd/v3/V3Response.hpp"
#include "etcd/v3/Member.hpp"
#include "etcd/v3/action_constants.hpp" #include "etcd/v3/action_constants.hpp"
void etcdv3::V3Response::set_error_code(int code) void etcdv3::V3Response::set_error_code(int code) { error_code = code; }
{
error_code = code;
}
void etcdv3::V3Response::set_error_message(std::string msg) void etcdv3::V3Response::set_error_message(std::string msg) {
{
error_message = msg; error_message = msg;
} }
int etcdv3::V3Response::get_index() const int64_t etcdv3::V3Response::get_index() const { return index; }
{
return index;
}
std::string const & etcdv3::V3Response::get_action() const std::string const& etcdv3::V3Response::get_action() const { return action; }
{
return action;
}
int etcdv3::V3Response::get_error_code() const int etcdv3::V3Response::get_error_code() const { return error_code; }
{
return error_code;
}
std::string const & etcdv3::V3Response::get_error_message() const std::string const& etcdv3::V3Response::get_error_message() const {
{
return error_message; return error_message;
} }
void etcdv3::V3Response::set_action(std::string action) void etcdv3::V3Response::set_action(std::string action) {
{
this->action = action; this->action = action;
} }
std::vector<etcdv3::KeyValue> const & etcdv3::V3Response::get_values() const std::vector<etcdv3::KeyValue> const& etcdv3::V3Response::get_values() const {
{
return values; return values;
} }
std::vector<etcdv3::KeyValue> const & etcdv3::V3Response::get_prev_values() const std::vector<etcdv3::KeyValue> const& etcdv3::V3Response::get_prev_values()
{ const {
return prev_values; return prev_values;
} }
etcdv3::KeyValue const & etcdv3::V3Response::get_value() const etcdv3::KeyValue const& etcdv3::V3Response::get_value() const { return value; }
{
return value;
}
etcdv3::KeyValue const & etcdv3::V3Response::get_prev_value() const etcdv3::KeyValue const& etcdv3::V3Response::get_prev_value() const {
{
return prev_value; return prev_value;
} }
bool etcdv3::V3Response::has_values() const bool etcdv3::V3Response::has_values() const { return values.size() > 0; }
{
return values.size() > 0; int64_t etcdv3::V3Response::get_compact_revision() const {
return compact_revision;
} }
void etcdv3::V3Response::set_lock_key(std::string const &key) { void etcdv3::V3Response::set_compact_revision(const int64_t compact_revision) {
this->compact_revision = compact_revision;
}
int64_t etcdv3::V3Response::get_watch_id() const { return watch_id; }
void etcdv3::V3Response::set_watch_id(const int64_t watch_id) {
this->watch_id = watch_id;
}
void etcdv3::V3Response::set_lock_key(std::string const& key) {
this->lock_key = key; this->lock_key = key;
} }
std::string const & etcdv3::V3Response::get_lock_key() const { std::string const& etcdv3::V3Response::get_lock_key() const {
return this->lock_key; return this->lock_key;
} }
std::vector<mvccpb::Event> const & etcdv3::V3Response::get_events() const { void etcdv3::V3Response::set_name(std::string const& name) {
this->name = name;
}
std::string const& etcdv3::V3Response::get_name() const { return this->name; }
std::vector<mvccpb::Event> const& etcdv3::V3Response::get_events() const {
return this->events; return this->events;
} }
uint64_t etcdv3::V3Response::get_cluster_id() const { return this->cluster_id; }
uint64_t etcdv3::V3Response::get_member_id() const { return this->member_id; }
uint64_t etcdv3::V3Response::get_raft_term() const { return this->raft_term; }
std::vector<int64_t> const& etcdv3::V3Response::get_leases() const {
return this->leases;
}
std::vector<etcdv3::Member> const& etcdv3::V3Response::get_members() const {
return this->members;
}

View File

@ -1,12 +1,72 @@
#include "etcd/v3/action_constants.hpp" #include "etcd/v3/action_constants.hpp"
char const * etcdv3::CREATE_ACTION = "create"; char const* etcdv3::CREATE_ACTION = "create";
char const * etcdv3::COMPARESWAP_ACTION = "compareAndSwap"; char const* etcdv3::COMPARESWAP_ACTION = "compareAndSwap";
char const * etcdv3::UPDATE_ACTION = "update"; char const* etcdv3::UPDATE_ACTION = "update";
char const * etcdv3::SET_ACTION = "set"; char const* etcdv3::SET_ACTION = "set";
char const * etcdv3::GET_ACTION = "get"; char const* etcdv3::GET_ACTION = "get";
char const * etcdv3::DELETE_ACTION = "delete"; char const* etcdv3::PUT_ACTION = "set"; // alias
char const * etcdv3::COMPAREDELETE_ACTION = "compareAndDelete"; char const* etcdv3::DELETE_ACTION = "delete";
char const * etcdv3::LOCK_ACTION = "lock"; char const* etcdv3::COMPAREDELETE_ACTION = "compareAndDelete";
char const * etcdv3::UNLOCK_ACTION = "unlock"; char const* etcdv3::LOCK_ACTION = "lock";
char const * etcdv3::TXN_ACTION = "txn"; char const* etcdv3::UNLOCK_ACTION = "unlock";
char const* etcdv3::TXN_ACTION = "txn";
char const* etcdv3::WATCH_ACTION = "watch";
char const* etcdv3::LEASEGRANT = "leasegrant";
char const* etcdv3::LEASEREVOKE = "leaserevoke";
char const* etcdv3::LEASEKEEPALIVE = "leasekeepalive";
char const* etcdv3::LEASETIMETOLIVE = "leasetimetolive";
char const* etcdv3::LEASELEASES = "leaseleases";
char const* etcdv3::ADDMEMBER = "addmember";
char const* etcdv3::LISTMEMBER = "listmember";
char const* etcdv3::REMOVEMEMBER = "removemember";
char const* etcdv3::CAMPAIGN_ACTION = "campaign";
char const* etcdv3::PROCLAIM_ACTION = "preclaim";
char const* etcdv3::LEADER_ACTION = "leader";
char const* etcdv3::OBSERVE_ACTION = "obverse";
char const* etcdv3::RESIGN_ACTION = "resign";
// see: noPrefixEnd in etcd, however c++ doesn't allows naive '\0' inside
// a string, thus we use std::string(1, '\x00') as the constructor.
std::string const etcdv3::NUL = std::string(1, '\x00');
char const* etcdv3::KEEPALIVE_CREATE = "keepalive create";
char const* etcdv3::KEEPALIVE_WRITE = "keepalive write";
char const* etcdv3::KEEPALIVE_READ = "keepalive read";
char const* etcdv3::KEEPALIVE_DONE = "keepalive done";
char const* etcdv3::KEEPALIVE_FINISH = "keepalive finish";
char const* etcdv3::WATCH_CREATE = "watch create";
char const* etcdv3::WATCH_WRITE = "watch write";
char const* etcdv3::WATCH_WRITE_CANCEL = "watch write cancel";
char const* etcdv3::WATCH_WRITES_DONE = "watch writes done";
char const* etcdv3::WATCH_FINISH = "watch finish";
char const* etcdv3::ELECTION_OBSERVE_CREATE = "observe create";
char const* etcdv3::ELECTION_OBSERVE_FINISH = "observe finish";
const int etcdv3::ERROR_GRPC_OK = 0;
const int etcdv3::ERROR_GRPC_CANCELLED = 1;
const int etcdv3::ERROR_GRPC_UNKNOWN = 2;
const int etcdv3::ERROR_GRPC_INVALID_ARGUMENT = 3;
const int etcdv3::ERROR_GRPC_DEADLINE_EXCEEDED = 4;
const int etcdv3::ERROR_GRPC_NOT_FOUND = 5;
const int etcdv3::ERROR_GRPC_ALREADY_EXISTS = 6;
const int etcdv3::ERROR_GRPC_PERMISSION_DENIED = 7;
const int etcdv3::ERROR_GRPC_UNAUTHENTICATED = 16;
const int etcdv3::ERROR_GRPC_RESOURCE_EXHAUSTED = 8;
const int etcdv3::ERROR_GRPC_FAILED_PRECONDITION = 9;
const int etcdv3::ERROR_GRPC_ABORTED = 10;
const int etcdv3::ERROR_GRPC_OUT_OF_RANGE = 11;
const int etcdv3::ERROR_GRPC_UNIMPLEMENTED = 12;
const int etcdv3::ERROR_GRPC_INTERNAL = 13;
const int etcdv3::ERROR_GRPC_UNAVAILABLE = 14;
const int etcdv3::ERROR_GRPC_DATA_LOSS = 15;
const int etcdv3::ERROR_KEY_NOT_FOUND = 100;
const int etcdv3::ERROR_COMPARE_FAILED = 101;
const int etcdv3::ERROR_KEY_ALREADY_EXISTS = 105;
const int etcdv3::ERROR_ACTION_CANCELLED = 106;

View File

@ -5,45 +5,50 @@
#include "etcd/Client.hpp" #include "etcd/Client.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
TEST_CASE("setup with auth") TEST_CASE("setup with auth") {
{ etcd::Client* etcd = etcd::Client::WithUser(etcd_url, "root", "root");
etcd::Client *etcd = etcd::Client::WithUser("http://127.0.0.1:2379", "root", "root");
etcd->rmdir("/test", true).wait(); etcd->rmdir("/test", true).wait();
} }
TEST_CASE("add a new key after authenticate") TEST_CASE("add a new key after authenticate") {
{ etcd::Client* etcd = etcd::Client::WithUser(etcd_url, "root", "root");
etcd::Client *etcd = etcd::Client::WithUser("http://127.0.0.1:2379", "root", "root");
etcd->rmdir("/test", true).wait(); etcd->rmdir("/test", true).wait();
etcd::Response resp = etcd->add("/test/key1", "42").get(); etcd::Response resp = etcd->add("/test/key1", "42").get();
REQUIRE(0 == resp.error_code()); REQUIRE(0 == resp.error_code());
CHECK("create" == resp.action()); CHECK("create" == resp.action());
etcd::Value const & val = resp.value(); etcd::Value const& val = resp.value();
CHECK("42" == val.as_string()); CHECK("42" == val.as_string());
CHECK("/test/key1" == val.key()); CHECK("/test/key1" == val.key());
CHECK(!val.is_dir()); CHECK(!val.is_dir());
CHECK(0 < val.created_index()); CHECK(0 < val.created_index());
CHECK(0 < val.modified_index()); CHECK(0 < val.modified_index());
CHECK(1 == val.version());
CHECK(0 < resp.index()); CHECK(0 < resp.index());
CHECK(105 == etcd->add("/test/key1", "43").get().error_code()); // Key already exists CHECK(
CHECK(105 == etcd->add("/test/key1", "42").get().error_code()); // Key already exists etcd::ERROR_KEY_ALREADY_EXISTS ==
CHECK("Key already exists" == etcd->add("/test/key1", "42").get().error_message()); etcd->add("/test/key1", "43").get().error_code()); // Key already exists
CHECK(
etcd::ERROR_KEY_ALREADY_EXISTS ==
etcd->add("/test/key1", "42").get().error_code()); // Key already exists
CHECK("etcd-cpp-apiv3: key already exists" ==
etcd->add("/test/key1", "42").get().error_message());
} }
TEST_CASE("read a value from etcd") TEST_CASE("read a value from etcd") {
{ etcd::Client* etcd = etcd::Client::WithUser(etcd_url, "root", "root");
etcd::Client *etcd = etcd::Client::WithUser("http://127.0.0.1:2379", "root", "root");
etcd::Response resp = etcd->get("/test/key1").get(); etcd::Response resp = etcd->get("/test/key1").get();
CHECK("get" == resp.action()); CHECK("get" == resp.action());
REQUIRE(resp.is_ok()); REQUIRE(resp.is_ok());
REQUIRE(0 == resp.error_code()); REQUIRE(0 == resp.error_code());
CHECK("42" == resp.value().as_string()); CHECK("42" == resp.value().as_string());
CHECK("" == etcd->get("/test").get().value().as_string()); // key points to a directory CHECK("" == etcd->get("/test").get().value().as_string()); // key points to a
// directory
} }
TEST_CASE("cleanup") TEST_CASE("cleanup") {
{ etcd::Client* etcd = etcd::Client::WithUser(etcd_url, "root", "root");
etcd::Client *etcd = etcd::Client::WithUser("http://127.0.0.1:2379", "root", "root");
REQUIRE(0 == etcd->rmdir("/test", true).get().error_code()); REQUIRE(0 == etcd->rmdir("/test", true).get().error_code());
} }

View File

@ -5,18 +5,34 @@ add_custom_target(check ${CMAKE_COMMAND} -E env CTEST_OUTPUT_ON_FAILURE=1
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose ${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) WORKING_DIRECTORY ${PROJECT_BINARY_DIR})
add_custom_target(etcd_tests)
add_dependencies(check etcd_tests)
file(GLOB TEST_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") file(GLOB TEST_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
foreach(testfile ${TEST_FILES}) foreach(testfile ${TEST_FILES})
string(REGEX MATCH "^(.*)\\.[^.]*$" dummy ${testfile}) string(REGEX MATCH "^(.*)\\.[^.]*$" dummy ${testfile})
set(test_name ${CMAKE_MATCH_1}) set(test_name ${CMAKE_MATCH_1})
message(STATUS "Found unit_test - " ${test_name}) message(STATUS "Found unit_test - " ${test_name})
add_executable(${test_name} ${CMAKE_CURRENT_SOURCE_DIR}/${testfile}) if(BUILD_ETCD_TESTS)
add_executable(${test_name} ${CMAKE_CURRENT_SOURCE_DIR}/${testfile})
else()
add_executable(${test_name} EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/${testfile})
endif()
use_cxx(${test_name})
set_exceptions(${test_name})
add_test(NAME ${test_name} COMMAND $<TARGET_FILE:${test_name}>) add_test(NAME ${test_name} COMMAND $<TARGET_FILE:${test_name}>)
set_property(TARGET ${test_name} PROPERTY CXX_STANDARD 11)
target_include_directories(${test_name} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen) target_include_directories(${test_name} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen)
target_include_directories(${test_name} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen/proto) target_include_directories(${test_name} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../proto/gen/proto)
target_link_libraries(${test_name} etcd-cpp-api) target_link_libraries(${test_name} PRIVATE etcd-cpp-api)
add_dependencies(check ${test_name}) if(UNIX AND NOT APPLE)
if(CMAKE_VERSION VERSION_LESS 3.13)
target_link_libraries(${test_name} PRIVATE -Wl,--no-as-needed -lSegFault -Wl,--as-needed)
else()
target_link_options(${test_name} PRIVATE -Wl,--no-as-needed -lSegFault -Wl,--as-needed)
endif()
endif()
add_dependencies(etcd_tests ${test_name})
endforeach() endforeach()

109
tst/CampaignTest.cpp Normal file
View File

@ -0,0 +1,109 @@
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include <chrono>
#include <thread>
#include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp"
#include "etcd/Response.hpp"
#include "etcd/SyncClient.hpp"
#include "etcd/Value.hpp"
static std::string etcd_uri = etcdv3::detail::resolve_etcd_endpoints(
"http://127.0.0.1:2379,http://127.0.0.1:2479,http://127.0.0.1:2579");
TEST_CASE("campaign and leadership using keepalive") {
etcd::Client etcd(etcd_uri);
auto keepalive = etcd.leasekeepalive(5).get();
auto lease_id = keepalive->Lease();
std::cout << lease_id << std::endl;
std::string value = std::string("192.168.1.6:1880");
auto resp1 = etcd.campaign("/leader", lease_id, value).get();
if (0 == resp1.error_code()) {
std::cout << "became leader: " << resp1.index() << std::endl;
} else {
std::cout << "error code: " << resp1.error_code()
<< "error message: " << resp1.error_message() << std::endl;
assert(false);
}
std::cout << "finish campaign" << std::endl;
auto resp2 = etcd.leader("/leader").get();
CHECK(0 == resp2.error_code());
CHECK(value == resp2.value().as_string());
CHECK(resp1.value().key() == resp2.value().key());
std::cout << resp2.value().key() << std::endl;
std::cout << "finish leader" << std::endl;
auto resp3 = etcd.resign("/leader", resp1.value().lease(),
resp1.value().key(), resp1.value().created_index())
.get();
CHECK(0 == resp3.error_code());
std::cout << "finish resign" << std::endl;
}
TEST_CASE("concurrent campaign with grpc timeout") {
std::string value1 = std::string("192.168.1.6:1880");
std::string value2 = std::string("192.168.1.6:1890");
auto fn1 = [&]() {
etcd::Client etcd(etcd_uri);
auto keepalive = etcd.leasekeepalive(5).get();
auto lease_id = keepalive->Lease();
auto resp1 = etcd.campaign("/leader", lease_id, value1).get();
CHECK(0 == resp1.error_code());
std::this_thread::sleep_for(std::chrono::seconds(10));
// resign
auto resp2 = etcd.resign("/leader", resp1.value().lease(),
resp1.value().key(), resp1.value().created_index())
.get();
CHECK(0 == resp2.error_code());
};
auto fn2 = [&]() {
etcd::Client etcd(etcd_uri);
auto keepalive = etcd.leasekeepalive(5).get();
auto lease_id = keepalive->Lease();
// set client timeout
etcd.set_grpc_timeout(std::chrono::seconds(3));
std::this_thread::sleep_for(std::chrono::seconds(3));
auto resp1 = etcd.campaign("/leader", lease_id, value2).get();
std::cout << resp1.error_code() << resp1.error_message() << std::endl;
CHECK(0 != resp1.error_code());
// wait until success
while (true) {
resp1 = etcd.campaign("/leader", lease_id, value2).get();
if (resp1.error_code() == 0) {
// check value
auto resp2 = etcd.leader("/leader").get();
CHECK(0 == resp2.error_code());
CHECK(value2 == resp2.value().as_string());
CHECK(resp1.value().key() == resp2.value().key());
// break the loop
break;
}
}
auto resp2 = etcd.resign("/leader", resp1.value().lease(),
resp1.value().key(), resp1.value().created_index())
.get();
CHECK(0 == resp2.error_code());
};
std::thread t1(fn1);
std::thread t2(fn2);
t1.join();
t2.join();
}

115
tst/ElectionTest.cpp Normal file
View File

@ -0,0 +1,115 @@
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include <chrono>
#include <iostream>
#include <thread>
#include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
TEST_CASE("setup") {
etcd::Client etcd(etcd_url);
etcd.rmdir("/test", true).wait();
}
TEST_CASE("campaign and resign") {
etcd::Client etcd(etcd_url);
auto keepalive = etcd.leasekeepalive(60).get();
auto lease_id = keepalive->Lease();
// campaign
auto resp1 = etcd.campaign("test", lease_id, "xxxx").get();
REQUIRE(0 == resp1.error_code());
// leader
{
auto resp2 = etcd.leader("test").get();
REQUIRE(0 == resp2.error_code());
REQUIRE(resp1.value().key() == resp2.value().key());
REQUIRE("xxxx" == resp2.value().as_string());
}
// proclaim
auto resp3 = etcd.proclaim("test", lease_id, resp1.value().key(),
resp1.value().created_index(), "tttt")
.get();
REQUIRE(0 == resp3.error_code());
// leader
{
auto resp4 = etcd.leader("test").get();
REQUIRE(0 == resp4.error_code());
REQUIRE(resp1.value().key() == resp4.value().key());
REQUIRE("tttt" == resp4.value().as_string());
}
// resign
auto resp5 = etcd.resign("test", lease_id, resp1.value().key(),
resp1.value().created_index())
.get();
REQUIRE(0 == resp5.error_code());
}
TEST_CASE("campaign and observe") {
etcd::Client etcd(etcd_url);
auto keepalive = etcd.leasekeepalive(60).get();
auto lease_id = keepalive->Lease();
auto observer_thread = std::thread([&etcd]() {
std::unique_ptr<etcd::SyncClient::Observer> observer = etcd.observe("test");
// wait many change events, blocked execution
for (size_t i = 0; i < 10; ++i) {
etcd::Response resp = observer->WaitOnce();
std::cout << "observe " << resp.value().key()
<< " as the leader: " << resp.value().as_string() << std::endl;
}
std::cout << "finish the observe" << std::endl;
// cancel the observers
observer.reset(nullptr);
});
std::this_thread::sleep_for(std::chrono::seconds(1));
for (int i = 0; i < 5; ++i) {
// campaign
auto resp1 = etcd.campaign("test", lease_id, "xxxx").get();
REQUIRE(0 == resp1.error_code());
std::cout << "key " << resp1.value().key() << " becomes the leader"
<< std::endl;
// proclaim
auto resp3 = etcd.proclaim("test", lease_id, resp1.value().key(),
resp1.value().created_index(),
"tttt - " + std::to_string(i))
.get();
REQUIRE(0 == resp3.error_code());
// leader
{
auto resp4 = etcd.leader("test").get();
REQUIRE(0 == resp4.error_code());
REQUIRE(resp1.value().key() == resp4.value().key());
REQUIRE("tttt - " + std::to_string(i) == resp4.value().as_string());
}
// resign
auto resp5 = etcd.resign("test", lease_id, resp1.value().key(),
resp1.value().created_index())
.get();
REQUIRE(0 == resp5.error_code());
std::this_thread::sleep_for(std::chrono::seconds(1));
}
observer_thread.join();
}
TEST_CASE("cleanup") {
etcd::Client etcd(etcd_url);
etcd.rmdir("/test", true).get();
}

136
tst/EtcdMemberTest.cpp Normal file
View File

@ -0,0 +1,136 @@
#include <cmath>
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <chrono>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>
#include "etcd/Client.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
pid_t new_etcd_pid = 0;
pid_t start_etcd_server(const std::string& etcd_path,
const std::vector<std::string>& args) {
pid_t pid = fork();
if (pid == -1) {
std::cout << "Failed to fork process" << std::endl;
exit(EXIT_FAILURE);
} else if (pid == 0) {
std::vector<char*> c_args;
c_args.push_back(const_cast<char*>(etcd_path.c_str()));
for (const auto& arg : args) {
c_args.push_back(const_cast<char*>(arg.c_str()));
}
c_args.push_back(nullptr);
if (execvp(etcd_path.c_str(), c_args.data()) == -1) {
std::cout << "Failed to exec etcd process: " << std::strerror(errno)
<< std::endl;
exit(EXIT_FAILURE);
}
}
return pid;
}
TEST_CASE("add member") {
etcd::Client etcd(etcd_url);
etcd::Response res = etcd.list_member().get();
REQUIRE(res.is_ok());
CHECK(1 == res.members().size());
std::string member_name = res.members()[0].get_name();
std::string prev_peer_urls = res.members()[0].get_peerURLs()[0];
// Add a new member
std::string peer_urls = "http://127.0.0.1:33691";
std::string client_urls = "http://127.0.0.1:33690";
bool is_learner = false;
res = etcd.add_member(peer_urls, is_learner).get();
REQUIRE(res.is_ok());
// Create the directory for the new etcd server
std::string cmd = "mkdir -p /tmp/new_etcd_member";
system(cmd.c_str());
// Start a new etcd server
std::vector<std::string> args = {
"--name",
"new_etcd_member",
"--initial-advertise-peer-urls",
peer_urls,
"--listen-peer-urls",
peer_urls,
"--initial-cluster",
member_name + "=" + prev_peer_urls + ",new_etcd_member=" + peer_urls,
"--initial-cluster-state",
"existing",
"--listen-client-urls",
client_urls,
"--advertise-client-urls",
client_urls,
"--data-dir",
"/tmp/new_etcd_member",
};
new_etcd_pid = start_etcd_server("/usr/local/bin/etcd", args);
std::this_thread::sleep_for(std::chrono::seconds(30));
// Check the member's number
{
etcd::Response res = etcd.list_member().get();
REQUIRE(res.is_ok());
CHECK(2 == res.members().size());
}
}
TEST_CASE("member remove") {
etcd::Client etcd(etcd_url);
etcd::Response res = etcd.list_member().get();
REQUIRE(res.is_ok());
CHECK(2 == res.members().size());
uint64_t member_id = 0;
for (const auto& member : res.members()) {
if (member.get_name() == "new_etcd_member") {
member_id = member.get_id();
break;
}
}
REQUIRE(member_id != 0);
// Remove the new member
res = etcd.remove_member(member_id).get();
std::this_thread::sleep_for(std::chrono::seconds(30));
// Check whether the new etcd server is quited
{
int status;
pid_t wpid = waitpid(new_etcd_pid, &status, WNOHANG);
REQUIRE(wpid == new_etcd_pid);
REQUIRE(WIFEXITED(status));
REQUIRE(WEXITSTATUS(status) == 0);
}
// Remove the directory for the new etcd server
std::string cmd = "rm -rf /tmp/new_etcd_member";
system(cmd.c_str());
// Check the member's number
{
etcd::Response res = etcd.list_member().get();
REQUIRE(res.is_ok());
CHECK(1 == res.members().size());
}
}

30
tst/EtcdResolverTest.cpp Normal file
View File

@ -0,0 +1,30 @@
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include <iostream>
#include "etcd/Client.hpp"
static const std::string etcd_v4_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
static const std::string etcd_v6_url =
etcdv3::detail::resolve_etcd_endpoints("http://::1:2379");
// http://[ipv6]:port url
static const std::string etcd_ipv6_url =
etcdv3::detail::resolve_etcd_endpoints("http://[::1]:2379");
TEST_CASE("test ipv4 connection") {
std::cout << "ipv4 endpoints: " << etcd_v4_url << std::endl;
etcd::Client etcd(etcd_v4_url);
REQUIRE(etcd.head().get().is_ok());
}
TEST_CASE("test ipv6 connection") {
std::cout << "ipv6 endpoints: " << etcd_v6_url << std::endl;
etcd::Client etcd(etcd_v6_url);
REQUIRE(etcd.head().get().is_ok());
std::cout << "ipv6 endpoints: " << etcd_ipv6_url << std::endl;
etcd::Client etcd1(etcd_ipv6_url);
REQUIRE(etcd1.head().get().is_ok());
}

View File

@ -5,38 +5,46 @@
#include "etcd/SyncClient.hpp" #include "etcd/SyncClient.hpp"
static std::string etcd_uri("http://127.0.0.1:2379"); static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
TEST_CASE("sync operations") TEST_CASE("sync operations") {
{ etcd::SyncClient etcd(etcd_url);
etcd::SyncClient etcd(etcd_uri);
etcd.rmdir("/test", true); etcd.rmdir("/test", true);
etcd::Response res;
int64_t index;
// add // add
CHECK(0 == etcd.add("/test/key1", "42").error_code()); CHECK(0 == etcd.add("/test/key1", "42").error_code());
CHECK(105 == etcd.add("/test/key1", "42").error_code()); // Key already exists CHECK(etcd::ERROR_KEY_ALREADY_EXISTS ==
etcd.add("/test/key1", "41").error_code()); // Key already exists
CHECK("42" == etcd.get("/test/key1").value().as_string()); CHECK("42" == etcd.get("/test/key1").value().as_string());
// modify // modify
CHECK(0 == etcd.modify("/test/key1", "43").error_code()); CHECK(0 == etcd.modify("/test/key1", "43").error_code());
CHECK(100 == etcd.modify("/test/key2", "43").error_code()); // Key not found CHECK(etcd::ERROR_KEY_NOT_FOUND ==
CHECK("43" == etcd.modify("/test/key1", "42").prev_value().as_string()); etcd.modify("/test/key2", "43").error_code()); // Key not found
res = etcd.modify("/test/key1", "42");
CHECK(0 == res.error_code());
CHECK("43" == res.prev_value().as_string());
// set // set
CHECK(0 == etcd.set("/test/key1", "43").error_code()); // overwrite CHECK(0 == etcd.set("/test/key1", "43").error_code()); // overwrite
CHECK(0 == etcd.set("/test/key2", "43").error_code()); // create new CHECK(0 == etcd.set("/test/key2", "43").error_code()); // create new
CHECK("43" == etcd.set("/test/key2", "44").prev_value().as_string()); CHECK("43" == etcd.set("/test/key2", "44").prev_value().as_string());
CHECK("" == etcd.set("/test/key3", "44").prev_value().as_string()); CHECK("" == etcd.set("/test/key3", "44").prev_value().as_string());
// get // get
CHECK("43" == etcd.get("/test/key1").value().as_string()); CHECK("43" == etcd.get("/test/key1").value().as_string());
CHECK("44" == etcd.get("/test/key2").value().as_string()); CHECK("44" == etcd.get("/test/key2").value().as_string());
CHECK("44" == etcd.get("/test/key3").value().as_string()); CHECK("44" == etcd.get("/test/key3").value().as_string());
CHECK(100 == etcd.get("/test/key4").error_code()); // key not found CHECK(etcd::ERROR_KEY_NOT_FOUND ==
etcd.get("/test/key4").error_code()); // key not found
// rm // rm
CHECK(3 == etcd.ls("/test").keys().size()); CHECK(3 == etcd.ls("/test").keys().size());
CHECK(0 == etcd.rm("/test/key1").error_code()); CHECK(0 == etcd.rm("/test/key1").error_code());
CHECK(2 == etcd.ls("/test").keys().size()); CHECK(2 == etcd.ls("/test").keys().size());
// ls // ls
@ -45,73 +53,87 @@ TEST_CASE("sync operations")
etcd.set("/test/new_dir/key2", "value2"); etcd.set("/test/new_dir/key2", "value2");
CHECK(2 == etcd.ls("/test/new_dir").keys().size()); CHECK(2 == etcd.ls("/test/new_dir").keys().size());
// keys
etcd.set("/test/new_dir/key1", "value1");
etcd.set("/test/new_dir/key2", "value2");
CHECK(2 == etcd.keys("/test/new_dir").keys().size());
// rmdir // rmdir
CHECK(100 == etcd.rmdir("/test/new_dir").error_code()); // key not found CHECK(etcd::ERROR_KEY_NOT_FOUND ==
etcd.rmdir("/test/new_dir").error_code()); // key not found
CHECK(0 == etcd.rmdir("/test/new_dir", true).error_code()); CHECK(0 == etcd.rmdir("/test/new_dir", true).error_code());
// compare and swap // compare and swap
etcd.set("/test/key1", "42"); etcd.set("/test/key1", "42");
int index = etcd.modify_if("/test/key1", "43", "42").index(); index = etcd.modify_if("/test/key1", "43", "42").index();
CHECK(101 == etcd.modify_if("/test/key1", "44", "42").error_code()); CHECK(etcd::ERROR_COMPARE_FAILED ==
etcd.modify_if("/test/key1", "44", "42").error_code());
REQUIRE(etcd.modify_if("/test/key1", "44", index).is_ok()); REQUIRE(etcd.modify_if("/test/key1", "44", index).is_ok());
CHECK(101 == etcd.modify_if("/test/key1", "45", index).error_code()); CHECK(etcd::ERROR_COMPARE_FAILED ==
etcd.modify_if("/test/key1", "45", index).error_code());
// atomic compare-and-delete based on prevValue // atomic compare-and-delete based on prevValue
etcd.set("/test/key1", "42"); etcd.set("/test/key1", "42");
CHECK(101 == etcd.rm_if("/test/key1", "43").error_code()); CHECK(etcd::ERROR_COMPARE_FAILED ==
CHECK(0 == etcd.rm_if("/test/key1", "42").error_code()); etcd.rm_if("/test/key1", "43").error_code());
res = etcd.rm_if("/test/key1", "42");
CHECK(
(0 == res.error_code() || etcd::ERROR_KEY_NOT_FOUND == res.error_code()));
// atomic compare-and-delete based on prevIndex // atomic compare-and-delete based on prevIndex
index = etcd.set("/test/key1", "42").index(); index = etcd.set("/test/key1", "42").index();
CHECK(101 == etcd.rm_if("/test/key1", index - 1).error_code()); CHECK(etcd::ERROR_COMPARE_FAILED ==
CHECK(0 == etcd.rm_if("/test/key1", index).error_code()); etcd.rm_if("/test/key1", index - 1).error_code());
res = etcd.rm_if("/test/key1", index);
CHECK(
(0 == res.error_code() || etcd::ERROR_KEY_NOT_FOUND == res.error_code()));
//leasegrant // leasegrant
etcd::Response res = etcd.leasegrant(60); res = etcd.leasegrant(60);
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
CHECK(60 == res.value().ttl()); CHECK(60 == res.value().ttl());
CHECK(0 < res.value().lease()); CHECK(0 < res.value().lease());
int64_t leaseid = res.value().lease(); int64_t leaseid = res.value().lease();
//add with lease // add with lease
res = etcd.add("/test/key1111", "43", leaseid); res = etcd.add("/test/key1111", "43", leaseid);
REQUIRE(0 == res.error_code()); // overwrite REQUIRE(0 == res.error_code()); // overwrite
CHECK("create" == res.action()); CHECK("create" == res.action());
CHECK(leaseid == res.value().lease()); CHECK(leaseid == res.value().lease());
//set with lease // set with lease
res = etcd.set("/test/key1", "43", leaseid); res = etcd.set("/test/key1", "43", leaseid);
REQUIRE(0 == res.error_code()); REQUIRE(0 == res.error_code());
CHECK("set" == res.action()); CHECK("set" == res.action());
CHECK(leaseid == res.value().lease()); res = etcd.get("/test/key1");
CHECK(leaseid == res.value().lease());
//modify with lease // modify with lease
res = etcd.modify("/test/key1", "44", leaseid); res = etcd.modify("/test/key1", "44", leaseid);
REQUIRE(0 == res.error_code()); REQUIRE(0 == res.error_code());
CHECK("update" == res.action()); CHECK("update" == res.action());
CHECK(leaseid == res.value().lease()); CHECK(leaseid == res.value().lease());
CHECK("44" == res.value().as_string()); CHECK("44" == res.value().as_string());
res = etcd.modify_if("/test/key1", "45", "44", leaseid); res = etcd.modify_if("/test/key1", "45", "44", leaseid);
index = res.index(); index = res.index();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
CHECK("compareAndSwap" == res.action()); CHECK("compareAndSwap" == res.action());
CHECK(leaseid == res.value().lease()); CHECK(leaseid == res.value().lease());
CHECK("45" == res.value().as_string()); CHECK("45" == res.value().as_string());
res = etcd.modify_if("/test/key1", "44", index, leaseid); res = etcd.modify_if("/test/key1", "44", index, leaseid);
index = res.index(); index = res.index();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
CHECK("compareAndSwap" == res.action()); CHECK("compareAndSwap" == res.action());
CHECK(leaseid == res.value().lease()); CHECK(leaseid == res.value().lease());
CHECK("44" == res.value().as_string()); CHECK("44" == res.value().as_string());
REQUIRE(0 == etcd.rmdir("/test", true).error_code()); REQUIRE(0 == etcd.rmdir("/test", true).error_code());
} }
TEST_CASE("wait for a value change") TEST_CASE("wait for a value change") {
{ etcd::SyncClient etcd(etcd_url);
etcd::SyncClient etcd(etcd_uri);
etcd.set("/test/key1", "42"); etcd.set("/test/key1", "42");
std::thread watch_thrd([&]() { std::thread watch_thrd([&]() {
@ -127,9 +149,8 @@ TEST_CASE("wait for a value change")
REQUIRE(0 == etcd.rmdir("/test", true).error_code()); REQUIRE(0 == etcd.rmdir("/test", true).error_code());
} }
TEST_CASE("wait for a directory change") TEST_CASE("wait for a directory change") {
{ etcd::SyncClient etcd(etcd_url);
etcd::SyncClient etcd(etcd_uri);
std::thread watch_thrd1([&]() { std::thread watch_thrd1([&]() {
etcd::Response res = etcd.watch("/test", true); etcd::Response res = etcd.watch("/test", true);
@ -154,11 +175,10 @@ TEST_CASE("wait for a directory change")
REQUIRE(0 == etcd.rmdir("/test", true).error_code()); REQUIRE(0 == etcd.rmdir("/test", true).error_code());
} }
TEST_CASE("watch changes in the past") TEST_CASE("watch changes in the past") {
{ etcd::SyncClient etcd(etcd_url);
etcd::SyncClient etcd(etcd_uri);
int index = etcd.set("/test/key1", "42").index(); int64_t index = etcd.set("/test/key1", "42").index();
etcd.set("/test/key1", "43"); etcd.set("/test/key1", "43");
etcd.set("/test/key1", "44"); etcd.set("/test/key1", "44");
@ -181,7 +201,7 @@ TEST_CASE("watch changes in the past")
// TEST_CASE("request cancellation") // TEST_CASE("request cancellation")
// { // {
// etcd::Client etcd(etcd_uri); // etcd::Client etcd(etcd_url);
// etcd.set("/test/key1", "42").wait(); // etcd.set("/test/key1", "42").wait();
// pplx::task<etcd::Response> res = etcd.watch("/test/key1"); // pplx::task<etcd::Response> res = etcd.watch("/test/key1");

View File

@ -7,89 +7,105 @@
#include "etcd/Client.hpp" #include "etcd/Client.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
TEST_CASE("setup") TEST_CASE("setup") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
etcd.rmdir("/test", true).wait(); etcd.rmdir("/test", true).wait();
} }
TEST_CASE("add a new key") TEST_CASE("add a new key") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
etcd.rmdir("/test", true).wait(); etcd.rmdir("/test", true).wait();
etcd::Response resp = etcd.add("/test/key1", "42").get(); etcd::Response resp = etcd.add("/test/key1", "42").get();
REQUIRE(0 == resp.error_code()); REQUIRE(0 == resp.error_code());
CHECK("create" == resp.action()); CHECK("create" == resp.action());
etcd::Value const & val = resp.value(); etcd::Value const& val = resp.value();
CHECK("42" == val.as_string()); CHECK("42" == val.as_string());
CHECK("/test/key1" == val.key()); CHECK("/test/key1" == val.key());
CHECK(!val.is_dir()); CHECK(!val.is_dir());
CHECK(0 < val.created_index()); CHECK(0 < val.created_index());
CHECK(0 < val.modified_index()); CHECK(0 < val.modified_index());
CHECK(1 == val.version());
CHECK(0 < resp.index()); CHECK(0 < resp.index());
CHECK(105 == etcd.add("/test/key1", "43").get().error_code()); // Key already exists CHECK(etcd::ERROR_KEY_ALREADY_EXISTS ==
CHECK(105 == etcd.add("/test/key1", "42").get().error_code()); // Key already exists etcd.add("/test/key1", "43").get().error_code()); // Key already exists
CHECK("Key already exists" == etcd.add("/test/key1", "42").get().error_message()); CHECK(etcd::ERROR_KEY_ALREADY_EXISTS ==
etcd.add("/test/key1", "42").get().error_code()); // Key already exists
CHECK("etcd-cpp-apiv3: key already exists" ==
etcd.add("/test/key1", "42").get().error_message());
} }
TEST_CASE("read a value from etcd") TEST_CASE("read a value from etcd") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
etcd::Response resp = etcd.get("/test/key1").get(); etcd::Response resp = etcd.get("/test/key1").get();
CHECK("get" == resp.action()); CHECK("get" == resp.action());
REQUIRE(resp.is_ok()); REQUIRE(resp.is_ok());
REQUIRE(0 == resp.error_code()); REQUIRE(0 == resp.error_code());
CHECK("42" == resp.value().as_string()); CHECK("42" == resp.value().as_string());
CHECK("" == etcd.get("/test").get().value().as_string()); // key points to a directory CHECK("" == etcd.get("/test").get().value().as_string()); // key points to a
// directory
} }
TEST_CASE("simplified read") TEST_CASE("simplified read") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
CHECK("42" == etcd.get("/test/key1").get().value().as_string()); CHECK("42" == etcd.get("/test/key1").get().value().as_string());
CHECK(100 == etcd.get("/test/key2").get().error_code()); // Key not found CHECK(etcd::ERROR_KEY_NOT_FOUND ==
CHECK("" == etcd.get("/test/key2").get().value().as_string()); // Key not found etcd.get("/test/key2").get().error_code()); // Key not found
CHECK("" ==
etcd.get("/test/key2").get().value().as_string()); // Key not found
} }
TEST_CASE("modify a key") TEST_CASE("modify a key") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
etcd::Response resp = etcd.modify("/test/key1", "43").get(); // get
REQUIRE(0 == resp.error_code()); // overwrite etcd::Response resp = etcd.get("/test/key1").get();
REQUIRE(resp.is_ok());
int64_t revision = resp.value().modified_index();
CHECK("42" == resp.value().as_string());
// modify
resp = etcd.modify("/test/key1", "43").get();
REQUIRE(0 == resp.error_code()); // overwrite
CHECK("update" == resp.action()); CHECK("update" == resp.action());
CHECK(100 == etcd.modify("/test/key2", "43").get().error_code()); // Key not found CHECK(etcd::ERROR_KEY_NOT_FOUND ==
etcd.modify("/test/key2", "43").get().error_code()); // Key not found
CHECK("43" == etcd.modify("/test/key1", "42").get().prev_value().as_string()); CHECK("43" == etcd.modify("/test/key1", "42").get().prev_value().as_string());
// check previous
resp = etcd.get("/test/key1", revision).get();
REQUIRE(resp.is_ok());
CHECK("42" == resp.value().as_string());
} }
TEST_CASE("set a key") TEST_CASE("set a key") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
etcd::Response resp = etcd.set("/test/key1", "43").get(); etcd::Response resp = etcd.set("/test/key1", "43").get();
REQUIRE(0 == resp.error_code()); // overwrite REQUIRE(0 == resp.error_code()); // overwrite
CHECK("set" == resp.action()); CHECK("set" == resp.action());
CHECK(0 == etcd.set("/test/key2", "43").get().error_code()); // create new CHECK(0 == etcd.set("/test/key2", "43").get().error_code()); // create new
CHECK("43" == etcd.set("/test/key2", "44").get().prev_value().as_string()); CHECK("43" == etcd.set("/test/key2", "44").get().prev_value().as_string());
CHECK("" == etcd.set("/test/key3", "44").get().prev_value().as_string()); CHECK("" == etcd.set("/test/key3", "44").get().prev_value().as_string());
CHECK(0 == etcd.set("/test", "42").get().error_code()); // Not a file CHECK(0 == etcd.set("/test", "42").get().error_code()); // Not a file
//set with ttl // set with ttl
resp = etcd.set("/test/key1", "50", 10).get(); resp = etcd.set("/test/key1", "50").get();
REQUIRE(0 == resp.error_code()); // overwrite REQUIRE(0 == resp.error_code()); // overwrite
CHECK("set" == resp.action()); CHECK("set" == resp.action());
CHECK("43" == resp.prev_value().as_string()); CHECK("43" == resp.prev_value().as_string());
resp = etcd.get("/test/key1").get();
CHECK("50" == resp.value().as_string()); CHECK("50" == resp.value().as_string());
CHECK( 0 < resp.value().lease()); CHECK(0 == resp.value().lease());
} }
TEST_CASE("atomic compare-and-swap") TEST_CASE("atomic compare-and-swap") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
etcd.set("/test/key1", "42").wait(); etcd.set("/test/key1", "42").wait();
// modify success // modify success
etcd::Response res = etcd.modify_if("/test/key1", "43", "42").get(); etcd::Response res = etcd.modify_if("/test/key1", "43", "42").get();
int index = res.index();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
CHECK("compareAndSwap" == res.action()); CHECK("compareAndSwap" == res.action());
CHECK("43" == res.value().as_string()); CHECK("43" == res.value().as_string());
@ -97,48 +113,53 @@ TEST_CASE("atomic compare-and-swap")
// modify fails the second time // modify fails the second time
res = etcd.modify_if("/test/key1", "44", "42").get(); res = etcd.modify_if("/test/key1", "44", "42").get();
CHECK(!res.is_ok()); CHECK(!res.is_ok());
CHECK(101 == res.error_code()); CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code());
CHECK("Compare failed" == res.error_message()); CHECK("etcd-cpp-apiv3: compare failed" == res.error_message());
// modify fails the second time // modify fails on non-existing keys
res = etcd.modify_if("/test/key222", "44", "42").get(); res = etcd.modify_if("/test/key222", "44", "42").get();
CHECK(!res.is_ok()); CHECK(!res.is_ok());
CHECK(100 == res.error_code()); CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code());
CHECK("Key not found" == res.error_message()); CHECK("etcd-cpp-apiv3: compare failed" == res.error_message());
} }
TEST_CASE("delete a value") TEST_CASE("delete a value") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
etcd::Response resp = etcd.rm("/test/key11111").get(); etcd::Response resp = etcd.rm("/test/key11111").get();
CHECK(!resp.is_ok()); CHECK(!resp.is_ok());
CHECK(100 == resp.error_code()); CHECK(etcd::ERROR_KEY_NOT_FOUND == resp.error_code());
CHECK("Key not found" == resp.error_message()); CHECK("etcd-cpp-apiv3: key not found" == resp.error_message());
int64_t index = etcd.get("/test/key1").get().index();
int64_t create_index = etcd.get("/test/key1").get().value().created_index();
int64_t modify_index = etcd.get("/test/key1").get().value().modified_index();
int64_t version = etcd.get("/test/key1").get().value().version();
int head_index = etcd.head().get().index();
CHECK(index == head_index);
int index = etcd.get("/test/key1").get().index();
int create_index = etcd.get("/test/key1").get().value().created_index();
int modify_index = etcd.get("/test/key1").get().value().modified_index();
resp = etcd.rm("/test/key1").get(); resp = etcd.rm("/test/key1").get();
CHECK("43" == resp.prev_value().as_string()); CHECK("43" == resp.prev_value().as_string());
CHECK( "/test/key1" == resp.prev_value().key()); CHECK("/test/key1" == resp.prev_value().key());
CHECK( create_index == resp.prev_value().created_index()); CHECK(create_index == resp.prev_value().created_index());
CHECK( modify_index == resp.prev_value().modified_index()); CHECK(modify_index == resp.prev_value().modified_index());
CHECK(version == resp.prev_value().version());
CHECK("delete" == resp.action()); CHECK("delete" == resp.action());
CHECK( modify_index == resp.value().modified_index()); CHECK(create_index == resp.value().created_index());
CHECK( create_index == resp.value().created_index()); CHECK(modify_index == resp.value().modified_index());
CHECK("" == resp.value().as_string()); CHECK(version == resp.value().version());
CHECK( "/test/key1" == resp.value().key()); CHECK("43" == resp.value().as_string());
CHECK("/test/key1" == resp.value().key());
} }
TEST_CASE("atomic compare-and-delete based on prevValue") TEST_CASE("atomic compare-and-delete based on prevValue") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
etcd.set("/test/key1", "42").wait(); etcd.set("/test/key1", "42").wait();
etcd::Response res = etcd.rm_if("/test/key1", "43").get(); etcd::Response res = etcd.rm_if("/test/key1", "43").get();
CHECK(!res.is_ok()); CHECK(!res.is_ok());
CHECK(101 == res.error_code()); CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code());
CHECK("Compare failed" == res.error_message()); CHECK("etcd-cpp-apiv3: compare failed" == res.error_message());
res = etcd.rm_if("/test/key1", "42").get(); res = etcd.rm_if("/test/key1", "42").get();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
@ -146,15 +167,14 @@ TEST_CASE("atomic compare-and-delete based on prevValue")
CHECK("42" == res.prev_value().as_string()); CHECK("42" == res.prev_value().as_string());
} }
TEST_CASE("atomic compare-and-delete based on prevIndex") TEST_CASE("atomic compare-and-delete based on prevIndex") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379"); int64_t index = etcd.set("/test/key1", "42").get().index();
int index = etcd.set("/test/key1", "42").get().index();
etcd::Response res = etcd.rm_if("/test/key1", index - 1).get(); etcd::Response res = etcd.rm_if("/test/key1", index - 1).get();
CHECK(!res.is_ok()); CHECK(!res.is_ok());
CHECK(101 == res.error_code()); CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code());
CHECK("Compare failed" == res.error_message()); CHECK("etcd-cpp-apiv3: compare failed" == res.error_message());
res = etcd.rm_if("/test/key1", index).get(); res = etcd.rm_if("/test/key1", index).get();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
@ -162,15 +182,13 @@ TEST_CASE("atomic compare-and-delete based on prevIndex")
CHECK("42" == res.prev_value().as_string()); CHECK("42" == res.prev_value().as_string());
} }
TEST_CASE("deep atomic compare-and-swap") {
TEST_CASE("deep atomic compare-and-swap") etcd::Client etcd(etcd_url);
{
etcd::Client etcd("http://127.0.0.1:2379");
etcd.set("/test/key1", "42").wait(); etcd.set("/test/key1", "42").wait();
// modify success // modify success
etcd::Response res = etcd.modify_if("/test/key1", "43", "42").get(); etcd::Response res = etcd.modify_if("/test/key1", "43", "42").get();
int index = res.index(); int64_t index = res.index();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
CHECK("compareAndSwap" == res.action()); CHECK("compareAndSwap" == res.action());
CHECK("43" == res.value().as_string()); CHECK("43" == res.value().as_string());
@ -178,8 +196,8 @@ TEST_CASE("deep atomic compare-and-swap")
// modify fails the second time // modify fails the second time
res = etcd.modify_if("/test/key1", "44", "42").get(); res = etcd.modify_if("/test/key1", "44", "42").get();
CHECK(!res.is_ok()); CHECK(!res.is_ok());
CHECK(101 == res.error_code()); CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code());
CHECK("Compare failed" == res.error_message()); CHECK("etcd-cpp-apiv3: compare failed" == res.error_message());
// succes with the correct index // succes with the correct index
res = etcd.modify_if("/test/key1", "44", index).get(); res = etcd.modify_if("/test/key1", "44", index).get();
@ -190,13 +208,65 @@ TEST_CASE("deep atomic compare-and-swap")
// index changes so second modify fails // index changes so second modify fails
res = etcd.modify_if("/test/key1", "45", index).get(); res = etcd.modify_if("/test/key1", "45", index).get();
CHECK(!res.is_ok()); CHECK(!res.is_ok());
CHECK(101 == res.error_code()); CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code());
CHECK("Compare failed" == res.error_message()); CHECK("etcd-cpp-apiv3: compare failed" == res.error_message());
} }
TEST_CASE("list a directory") TEST_CASE("using binary keys and values, raw char pointer doesn't work") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379"); etcd.rmdir("/test", true).wait();
{
etcd::Response resp = etcd.put("/test/key1\0xyz", "42\0foo").get();
REQUIRE(resp.is_ok());
}
{
// should not exist
etcd::Response resp = etcd.get(std::string("/test/key1\0xyz", 14)).get();
CHECK(!resp.is_ok());
CHECK(etcd::ERROR_KEY_NOT_FOUND == resp.error_code());
}
{
// should exist
etcd::Response resp = etcd.get(std::string("/test/key1", 10)).get();
REQUIRE(resp.is_ok());
CHECK(std::string("42") == resp.value().as_string());
CHECK(std::string("42\0foo", 6) != resp.value().as_string());
}
{
// should exist, but different value
etcd::Response resp = etcd.get("/test/key1\0xyz").get();
REQUIRE(resp.is_ok());
CHECK(std::string("42") == resp.value().as_string());
CHECK(std::string("42\0foo", 6) != resp.value().as_string());
}
}
TEST_CASE("using binary keys and values, std::string is ok for \\0") {
etcd::Client etcd(etcd_url);
etcd.rmdir("/test", true).wait();
{
etcd::Response resp =
etcd.put(std::string("/test/key1\0xyz", 14), std::string("42\0foo", 6))
.get();
REQUIRE(resp.is_ok());
}
{
// should not exist
etcd::Response resp = etcd.get("/test/key1").get();
CHECK(!resp.is_ok());
CHECK(etcd::ERROR_KEY_NOT_FOUND == resp.error_code());
}
{
// should exist
etcd::Response resp = etcd.get(std::string("/test/key1\0xyz", 14)).get();
REQUIRE(resp.is_ok());
CHECK(std::string("42") != resp.value().as_string());
CHECK(std::string("42\0foo", 6) == resp.value().as_string());
}
}
TEST_CASE("list a directory") {
etcd::Client etcd(etcd_url);
CHECK(0 == etcd.ls("/test/new_dir").get().keys().size()); CHECK(0 == etcd.ls("/test/new_dir").get().keys().size());
etcd.set("/test/new_dir/key1", "value1").wait(); etcd.set("/test/new_dir/key1", "value1").wait();
@ -223,12 +293,16 @@ TEST_CASE("list a directory")
CHECK(0 == etcd.ls("/test/new_dir/key1").get().error_code()); CHECK(0 == etcd.ls("/test/new_dir/key1").get().error_code());
// list with empty prefix
resp = etcd.ls("").get();
CHECK(0 == resp.error_code());
CHECK(0 < resp.keys().size());
CHECK(etcd.rmdir("/test/new_dir", true).get().is_ok()); CHECK(etcd.rmdir("/test/new_dir", true).get().is_ok());
} }
TEST_CASE("list by range") TEST_CASE("list by range") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
CHECK(0 == etcd.ls("/test/new_dir").get().keys().size()); CHECK(0 == etcd.ls("/test/new_dir").get().keys().size());
etcd.set("/test/new_dir/key0", "value0").wait(); etcd.set("/test/new_dir/key0", "value0").wait();
@ -237,13 +311,15 @@ TEST_CASE("list by range")
etcd.set("/test/new_dir/key3", "value3").wait(); etcd.set("/test/new_dir/key3", "value3").wait();
etcd.set("/test/new_dir/key4", "value4").wait(); etcd.set("/test/new_dir/key4", "value4").wait();
etcd::Response resp1 = etcd.ls("/test/new_dir/key1", "/test/new_dir/key3").get(); etcd::Response resp1 =
etcd.ls("/test/new_dir/key1", "/test/new_dir/key3").get();
REQUIRE(resp1.is_ok()); REQUIRE(resp1.is_ok());
CHECK("get" == resp1.action()); CHECK("get" == resp1.action());
REQUIRE(2 == resp1.keys().size()); REQUIRE(2 == resp1.keys().size());
REQUIRE(2 == resp1.values().size()); REQUIRE(2 == resp1.values().size());
etcd::Response resp2 = etcd.ls("/test/new_dir/key1", "/test/new_dir/key4").get(); etcd::Response resp2 =
etcd.ls("/test/new_dir/key1", "/test/new_dir/key4").get();
REQUIRE(resp2.is_ok()); REQUIRE(resp2.is_ok());
CHECK("get" == resp2.action()); CHECK("get" == resp2.action());
REQUIRE(3 == resp2.keys().size()); REQUIRE(3 == resp2.keys().size());
@ -255,7 +331,10 @@ TEST_CASE("list by range")
REQUIRE(4 == resp3.keys().size()); REQUIRE(4 == resp3.keys().size());
REQUIRE(4 == resp3.values().size()); REQUIRE(4 == resp3.values().size());
etcd::Response resp4 = etcd.ls("/test/new_dir/key1", etcdv3::detail::string_plus_one("/test/new_dir/key")).get(); etcd::Response resp4 =
etcd.ls("/test/new_dir/key1",
etcdv3::detail::string_plus_one("/test/new_dir/key"))
.get();
REQUIRE(resp4.is_ok()); REQUIRE(resp4.is_ok());
CHECK("get" == resp4.action()); CHECK("get" == resp4.action());
REQUIRE(4 == resp4.keys().size()); REQUIRE(4 == resp4.keys().size());
@ -266,19 +345,47 @@ TEST_CASE("list by range")
CHECK(etcd.rmdir("/test/new_dir", true).get().is_ok()); CHECK(etcd.rmdir("/test/new_dir", true).get().is_ok());
} }
TEST_CASE("delete a directory") TEST_CASE("list by range, w/o values") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379"); CHECK(0 == etcd.ls("/test/new_dir").get().keys().size());
etcd.set("/test/new_dir/key0", "value0").wait();
etcd.set("/test/new_dir/key1", "value1").wait();
etcd.set("/test/new_dir/key2", "value2").wait();
etcd.set("/test/new_dir/key3", "value3").wait();
etcd.set("/test/new_dir/key4", "value4").wait();
etcd::Response resp1 =
etcd.ls("/test/new_dir/key1", "/test/new_dir/key2").get();
REQUIRE(resp1.is_ok());
CHECK("get" == resp1.action());
REQUIRE(1 == resp1.keys().size());
REQUIRE(1 == resp1.values().size());
REQUIRE(resp1.values()[0].as_string() == "value1");
etcd::Response resp2 =
etcd.keys("/test/new_dir/key1", "/test/new_dir/key2").get();
REQUIRE(resp1.is_ok());
CHECK("get" == resp2.action());
REQUIRE(1 == resp2.keys().size());
REQUIRE(1 == resp2.values().size());
REQUIRE(resp2.values()[0].as_string() == "");
CHECK(etcd.rmdir("/test/new_dir", true).get().is_ok());
}
TEST_CASE("delete a directory") {
etcd::Client etcd(etcd_url);
etcd.set("/test/new_dir/key1", "value1").wait(); etcd.set("/test/new_dir/key1", "value1").wait();
etcd.set("/test/new_dir/key2", "value2").wait(); etcd.set("/test/new_dir/key2", "value2").wait();
etcd.set("/test/new_dir/key3", "value3").wait(); etcd.set("/test/new_dir/key3", "value3").wait();
CHECK(100 == etcd.rmdir("/test/new_dir").get().error_code()); // key not found CHECK(etcd::ERROR_KEY_NOT_FOUND ==
etcd.rmdir("/test/new_dir").get().error_code()); // key not found
etcd::Response resp = etcd.ls("/test/new_dir").get(); etcd::Response resp = etcd.ls("/test/new_dir").get();
resp = etcd.rmdir("/test/new_dir", true).get(); resp = etcd.rmdir("/test/new_dir", true).get();
int index = resp.index();
CHECK("delete" == resp.action()); CHECK("delete" == resp.action());
REQUIRE(3 == resp.keys().size()); REQUIRE(3 == resp.keys().size());
CHECK("/test/new_dir/key1" == resp.key(0)); CHECK("/test/new_dir/key1" == resp.key(0));
@ -286,23 +393,35 @@ TEST_CASE("delete a directory")
CHECK("value1" == resp.value(0).as_string()); CHECK("value1" == resp.value(0).as_string());
CHECK("value2" == resp.value(1).as_string()); CHECK("value2" == resp.value(1).as_string());
resp = etcd.rmdir("/test/dirnotfound", true).get(); resp = etcd.rmdir("/test/dirnotfound", true).get();
CHECK(!resp.is_ok()); CHECK(!resp.is_ok());
CHECK(100 == resp.error_code()); CHECK(etcd::ERROR_KEY_NOT_FOUND == resp.error_code());
CHECK("Key not found" == resp.error_message()); CHECK("etcd-cpp-apiv3: key not found" == resp.error_message());
resp = etcd.rmdir("/test/new_dir", false).get(); resp = etcd.rmdir("/test/new_dir", false).get();
CHECK(!resp.is_ok()); CHECK(!resp.is_ok());
CHECK(100 == resp.error_code()); CHECK(etcd::ERROR_KEY_NOT_FOUND == resp.error_code());
CHECK("Key not found" == resp.error_message()); CHECK("etcd-cpp-apiv3: key not found" == resp.error_message());
} }
TEST_CASE("delete by range") TEST_CASE("delete all keys with rmdir(\"\", true)") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379"); etcd.rmdir("", true).wait();
CHECK(100 == etcd.rmdir("/test/new_dir").get().error_code()); // key not found etcd.set("/test/new_dir/key1", "value1").wait();
etcd.set("/test/new_dir/key2", "value2").wait();
etcd.set("/test/new_dir/key3", "value3").wait();
auto resp = etcd.rmdir("", true).get();
CHECK(resp.is_ok());
CHECK(resp.values().size() == 3);
}
TEST_CASE("delete by range") {
etcd::Client etcd(etcd_url);
CHECK(etcd::ERROR_KEY_NOT_FOUND ==
etcd.rmdir("/test/new_dir").get().error_code()); // key not found
etcd::Response resp = etcd.ls("/test/new_dir").get(); etcd::Response resp = etcd.ls("/test/new_dir").get();
etcd.set("/test/new_dir/key1", "value1").wait(); etcd.set("/test/new_dir/key1", "value1").wait();
@ -319,16 +438,15 @@ TEST_CASE("delete by range")
CHECK("value2" == resp.value(1).as_string()); CHECK("value2" == resp.value(1).as_string());
} }
TEST_CASE("wait for a value change") TEST_CASE("wait for a value change") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
etcd.set("/test/key1", "42").wait(); etcd.set("/test/key1", "42").wait();
pplx::task<etcd::Response> res = etcd.watch("/test/key1"); pplx::task<etcd::Response> res = etcd.watch("/test/key1");
CHECK(!res.is_done()); CHECK(!res.is_done());
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
CHECK(!res.is_done()); CHECK(!res.is_done());
etcd.set("/test/key1", "43").get(); etcd.set("/test/key1", "43").get();
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
REQUIRE(res.is_done()); REQUIRE(res.is_done());
@ -337,9 +455,8 @@ TEST_CASE("wait for a value change")
CHECK("42" == res.get().prev_value().as_string()); CHECK("42" == res.get().prev_value().as_string());
} }
TEST_CASE("wait for a directory change") TEST_CASE("wait for a directory change") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
pplx::task<etcd::Response> res = etcd.watch("/test", true); pplx::task<etcd::Response> res = etcd.watch("/test", true);
CHECK(!res.is_done()); CHECK(!res.is_done());
@ -364,16 +481,18 @@ TEST_CASE("wait for a directory change")
CHECK("45" == res2.get().value().as_string()); CHECK("45" == res2.get().value().as_string());
} }
TEST_CASE("watch changes in the past") TEST_CASE("watch changes in the past") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
REQUIRE(0 == etcd.rmdir("/test", true).get().error_code()); REQUIRE(0 == etcd.rmdir("/test", true).get().error_code());
int index = etcd.set("/test/key1", "42").get().index(); int64_t index = etcd.set("/test/key1", "42").get().index();
etcd.set("/test/key1", "43").wait(); etcd.set("/test/key1", "43").wait();
etcd.set("/test/key1", "44").wait(); etcd.set("/test/key1", "44").wait();
etcd.set("/test/key1", "45").wait(); etcd.set("/test/key1", "45").wait();
int64_t head_index = etcd.head().get().index();
CHECK(index + 3 == head_index);
etcd::Response res = etcd.watch("/test/key1", ++index).get(); etcd::Response res = etcd.watch("/test/key1", ++index).get();
CHECK("set" == res.action()); CHECK("set" == res.action());
CHECK("43" == res.value().as_string()); CHECK("43" == res.value().as_string());
@ -388,17 +507,19 @@ TEST_CASE("watch changes in the past")
CHECK("45" == res.value().as_string()); CHECK("45" == res.value().as_string());
} }
TEST_CASE("watch range changes in the past") TEST_CASE("watch range changes in the past") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
REQUIRE(0 == etcd.rmdir("/test", true).get().error_code()); REQUIRE(0 == etcd.rmdir("/test", true).get().error_code());
int index = etcd.set("/test/key1", "42").get().index(); int64_t index = etcd.set("/test/key1", "42").get().index();
etcd.set("/test/key1", "43").wait(); etcd.set("/test/key1", "43").wait();
etcd.set("/test/key2", "44").wait(); etcd.set("/test/key2", "44").wait();
etcd.set("/test/key3", "45").wait(); etcd.set("/test/key3", "45").wait();
etcd.set("/test/key4", "45").wait(); etcd.set("/test/key4", "45").wait();
int64_t head_index = etcd.head().get().index();
CHECK(index + 4 == head_index);
etcd::Response res; etcd::Response res;
res = etcd.watch("/test/key1", "/test/key4", index).get(); res = etcd.watch("/test/key1", "/test/key4", index).get();
@ -412,98 +533,120 @@ TEST_CASE("watch range changes in the past")
} }
TEST_CASE("watch multiple keys and use promise") { TEST_CASE("watch multiple keys and use promise") {
etcd::Client etcd("http://127.0.0.1:2379"); etcd::Client etcd(etcd_url);
int start_index = etcd.set("/test/key1", "value1").get().index(); int64_t start_index = etcd.set("/test/key1", "value1").get().index();
etcd.set("/test/key2", "value2").get(); etcd.set("/test/key2", "value2").get();
pplx::task<size_t> res = etcd.watch("/test", start_index, true) pplx::task<size_t> res =
.then([](pplx::task<etcd::Response> const &resp_task) -> size_t { etcd.watch("/test", start_index, true)
auto const &resp = resp_task.get(); .then([](pplx::task<etcd::Response> const& resp_task) -> size_t {
return resp.events().size(); auto const& resp = resp_task.get();
}); return resp.events().size();
});
size_t event_size = res.get(); size_t event_size = res.get();
CHECK(2 == event_size); CHECK(2 == event_size);
} }
TEST_CASE("lease grant") TEST_CASE("lease grant") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
etcd::Response res = etcd.leasegrant(60).get(); etcd::Response res = etcd.leasegrant(60).get();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
CHECK(60 == res.value().ttl()); CHECK(60 == res.value().ttl());
CHECK(0 < res.value().lease()); CHECK(0 < res.value().lease());
int64_t leaseid = res.value().lease(); int64_t leaseid = res.value().lease();
res = etcd.set("/test/key1", "43", leaseid).get(); res = etcd.set("/test/key1", "43", leaseid).get();
REQUIRE(0 == res.error_code()); // overwrite REQUIRE(0 == res.error_code()); // overwrite
CHECK("set" == res.action()); CHECK("set" == res.action());
CHECK(leaseid == res.value().lease()); res = etcd.get("/test/key1").get();
REQUIRE(0 == res.error_code()); // overwrite
CHECK(leaseid == res.value().lease());
//change leaseid // change with lease id
res = etcd.leasegrant(10).get(); res = etcd.leasegrant(10).get();
leaseid = res.value().lease(); leaseid = res.value().lease();
res = etcd.set("/test/key1", "43", leaseid).get(); res = etcd.set("/test/key1", "43", leaseid).get();
REQUIRE(0 == res.error_code()); // overwrite REQUIRE(0 == res.error_code()); // overwrite
CHECK("set" == res.action()); CHECK("set" == res.action());
res = etcd.get("/test/key1").get();
REQUIRE(0 == res.error_code()); // overwrite
CHECK(leaseid == res.value().lease()); CHECK(leaseid == res.value().lease());
//failure to attach lease id // failure to attach lease id
res = etcd.set("/test/key1", "43", leaseid+1).get(); res = etcd.set("/test/key1", "43", leaseid + 1).get();
REQUIRE(!res.is_ok()); REQUIRE(!res.is_ok());
REQUIRE(5 == res.error_code()); REQUIRE(5 == res.error_code());
CHECK("etcdserver: requested lease not found" == res.error_message()); CHECK("etcdserver: requested lease not found" == res.error_message());
res = etcd.modify("/test/key1", "44", leaseid).get(); res = etcd.modify("/test/key1", "44", leaseid).get();
REQUIRE(0 == res.error_code()); // overwrite REQUIRE(0 == res.error_code()); // overwrite
CHECK("update" == res.action()); CHECK("update" == res.action());
CHECK(leaseid == res.value().lease()); CHECK(leaseid == res.value().lease());
CHECK("44" == res.value().as_string()); CHECK("44" == res.value().as_string());
//failure to attach lease id // failure to attach invalid lease id
res = etcd.modify("/test/key1", "45", leaseid+1).get(); res = etcd.modify("/test/key1", "45", leaseid + 1).get();
REQUIRE(!res.is_ok()); REQUIRE(!res.is_ok());
REQUIRE(5 == res.error_code()); REQUIRE(5 == res.error_code());
CHECK("etcdserver: requested lease not found" == res.error_message()); CHECK("etcdserver: requested lease not found" == res.error_message());
res = etcd.modify_if("/test/key1", "45", "44", leaseid).get(); res = etcd.modify_if("/test/key1", "45", "44", leaseid).get();
int index = res.index(); int64_t index = res.index();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
CHECK("compareAndSwap" == res.action()); CHECK("compareAndSwap" == res.action());
CHECK("45" == res.value().as_string()); CHECK("45" == res.value().as_string());
//failure to attach lease id // failure to attach invalid lease id
res = etcd.modify_if("/test/key1", "46", "45", leaseid+1).get(); res = etcd.modify_if("/test/key1", "46", "45", leaseid + 1).get();
REQUIRE(!res.is_ok()); REQUIRE(!res.is_ok());
REQUIRE(5 == res.error_code()); REQUIRE(5 == res.error_code());
CHECK("etcdserver: requested lease not found" == res.error_message()); CHECK("etcdserver: requested lease not found" == res.error_message());
// succes with the correct index // succes with the correct index & lease id
res = etcd.modify_if("/test/key1", "44", index, leaseid).get(); res = etcd.modify_if("/test/key1", "44", index, leaseid).get();
index = res.index(); index = res.index();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
CHECK("compareAndSwap" == res.action()); CHECK("compareAndSwap" == res.action());
CHECK("44" == res.value().as_string()); CHECK("44" == res.value().as_string());
res = etcd.modify_if("/test/key1", "44", index, leaseid+1).get(); res = etcd.modify_if("/test/key1", "44", index, leaseid + 1).get();
REQUIRE(!res.is_ok()); REQUIRE(!res.is_ok());
REQUIRE(5 == res.error_code()); REQUIRE(5 == res.error_code());
CHECK("etcdserver: requested lease not found" == res.error_message()); CHECK("etcdserver: requested lease not found" == res.error_message());
res = etcd.add("/test/key11111", "43", leaseid).get(); res = etcd.add("/test/key11111", "43", leaseid).get();
REQUIRE(0 == res.error_code()); REQUIRE(0 == res.error_code());
CHECK("create" == res.action()); CHECK("create" == res.action());
CHECK(leaseid == res.value().lease()); CHECK(leaseid == res.value().lease());
//failure to attach lease id // failure to attach invalid lease id
res = etcd.set("/test/key11111", "43", leaseid+1).get(); res = etcd.set("/test/key11111", "43", leaseid + 1).get();
REQUIRE(!res.is_ok()); REQUIRE(!res.is_ok());
REQUIRE(5 == res.error_code()); REQUIRE(5 == res.error_code());
CHECK("etcdserver: requested lease not found" == res.error_message()); CHECK("etcdserver: requested lease not found" == res.error_message());
} }
TEST_CASE("cleanup") TEST_CASE("lease list") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379"); etcd::Response res = etcd.leasegrant(60).get();
REQUIRE(res.is_ok());
CHECK(60 == res.value().ttl());
CHECK(0 < res.value().lease());
int64_t leaseid = res.value().lease();
etcd::Response leasesresp = etcd.leases().get();
if (leasesresp.is_ok()) {
REQUIRE(leasesresp.is_ok());
auto const& leases = leasesresp.leases();
REQUIRE(leases.size() > 0);
CHECK(std::find(leases.begin(), leases.end(), leaseid) != leases.end());
} else {
REQUIRE(leasesresp.error_code() == etcdv3::ERROR_GRPC_UNIMPLEMENTED);
}
}
TEST_CASE("cleanup") {
etcd::Client etcd(etcd_url);
REQUIRE(0 == etcd.rmdir("/test", true).get().error_code()); REQUIRE(0 == etcd.rmdir("/test", true).get().error_code());
} }

44
tst/ForkTest.cpp Normal file
View File

@ -0,0 +1,44 @@
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include <unistd.h>
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <thread>
#include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
TEST_CASE("fork: set in child and get from self") {
pid_t pid = fork();
REQUIRE(pid >= 0);
if (pid == 0) {
// child
etcd::Client etcd(etcd_url);
etcd.set("/test/fork-key1", "fork: abcdefgh").wait();
std::cout << "child: set finished ..." << std::endl;
} else {
// self
etcd::Client etcd(etcd_url);
size_t check = 0;
while (check < 10) {
auto resp = etcd.get("/test/fork-key1").get();
if (!resp.is_ok()) {
check += 1;
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
} else {
CHECK(resp.value().as_string() == "fork: abcdefgh");
break;
}
}
std::cout << "self: get finished ..." << std::endl;
}
}

81
tst/KeepAliveTest.cpp Normal file
View File

@ -0,0 +1,81 @@
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include <chrono>
#include <thread>
#include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp"
#include "etcd/Response.hpp"
#include "etcd/SyncClient.hpp"
#include "etcd/Value.hpp"
static std::string etcd_uri =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
TEST_CASE("keepalive revoke and check if alive") {
etcd::Client etcd(etcd_uri);
// create a lease with 30 seconds TTL
auto keepalive = etcd.leasekeepalive(30).get();
auto lease_id = keepalive->Lease();
// revoke the lease before it reaches its TTL
etcd.leaserevoke(lease_id).wait();
// retrieves its TTL again, and it is now -1
auto response = etcd.leasetimetolive(lease_id).get();
REQUIRE(response.value().ttl() == -1);
// shorter than the TLL, or no sleep
std::this_thread::sleep_for(std::chrono::seconds(1));
// expect keep_alive->Check() to throw exception
#ifndef _ETCD_NO_EXCEPTIONS
REQUIRE_THROWS(keepalive->Check());
#endif
}
TEST_CASE("keepalive won't expire") {
etcd::Client etcd(etcd_uri);
const int64_t ttl = 3;
const std::string key = "key";
const std::string meta_str = "meta ....";
etcd::Response resp = etcd.leasegrant(ttl).get();
auto lease_id = resp.value().lease();
etcd.add(key, meta_str, lease_id);
#ifndef _ETCD_NO_EXCEPTIONS
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";
}
};
#else
std::function<void(std::exception_ptr)> handler;
#endif
etcd::KeepAlive keepalive(etcd, handler, ttl, lease_id);
std::this_thread::sleep_for(std::chrono::seconds(5));
}
TEST_CASE("keepalive auto-grant") {
etcd::Client etcd(etcd_uri);
// create a lease without pre-granted lease id
auto keepalive = std::make_shared<etcd::KeepAlive>(etcd, 10 /* ttl */);
auto lease_id = keepalive->Lease();
REQUIRE(lease_id != 0);
// sleep for a while, and cancel
std::this_thread::sleep_for(std::chrono::seconds(5));
keepalive->Cancel();
}

View File

@ -10,10 +10,11 @@
#include "etcd/Client.hpp" #include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp" #include "etcd/KeepAlive.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
TEST_CASE("lock and unlock") TEST_CASE("lock and unlock") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
// lock // lock
etcd::Response resp1 = etcd.lock("/test/abcd").get(); etcd::Response resp1 = etcd.lock("/test/abcd").get();
@ -28,9 +29,8 @@ TEST_CASE("lock and unlock")
REQUIRE(0 == resp2.error_code()); REQUIRE(0 == resp2.error_code());
} }
TEST_CASE("double lock will fail") TEST_CASE("double lock will fail") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
// lock // lock
etcd::Response resp1 = etcd.lock("/test/abcd").get(); etcd::Response resp1 = etcd.lock("/test/abcd").get();
@ -41,7 +41,7 @@ TEST_CASE("double lock will fail")
bool first_lock_release = false; bool first_lock_release = false;
std::string lock_key = resp1.lock_key(); std::string lock_key = resp1.lock_key();
auto second_lock_thr = std::thread([&](){ auto second_lock_thr = std::thread([&]() {
// lock again // lock again
etcd::Response resp2 = etcd.lock("/test/abcd").get(); etcd::Response resp2 = etcd.lock("/test/abcd").get();
CHECK("lock" == resp2.action()); CHECK("lock" == resp2.action());
@ -61,11 +61,13 @@ TEST_CASE("double lock will fail")
REQUIRE(0 == resp3.error_code()); REQUIRE(0 == resp3.error_code());
// create a duration // create a duration
first_lock_release = true; // using a duration longer than default lease TTL for lock (see:
// using a duration longer than default lease TTL for lock (see: DEFAULT_LEASE_TTL_FOR_LOCK) // DEFAULT_LEASE_TTL_FOR_LOCK)
std::this_thread::sleep_for(std::chrono::seconds(15)); std::this_thread::sleep_for(std::chrono::seconds(15));
// unlock the first lock // unlock the first lock
first_lock_release = true;
etcd::Response resp4 = etcd.unlock(lock_key).get(); etcd::Response resp4 = etcd.unlock(lock_key).get();
CHECK("unlock" == resp4.action()); CHECK("unlock" == resp4.action());
REQUIRE(resp4.is_ok()); REQUIRE(resp4.is_ok());
@ -82,22 +84,54 @@ TEST_CASE("double lock will fail")
REQUIRE(0 == resp5.error_code()); REQUIRE(0 == resp5.error_code());
} }
TEST_CASE("lock using lease") TEST_CASE("lock could be timeout") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
// setup the timeout
etcd.set_grpc_timeout(std::chrono::seconds(5));
// lock
etcd::Response resp1 = etcd.lock("/test/abcd").get();
CHECK("lock" == resp1.action());
REQUIRE(resp1.is_ok());
REQUIRE(0 == resp1.error_code());
auto lock_in_another_thread = std::thread([&]() {
// lock again
etcd::Response resp2 = etcd.lock("/test/abcd").get();
CHECK("lock" == resp2.action());
REQUIRE(resp2.is_grpc_timeout());
});
lock_in_another_thread.join();
// cleanup: unlock the second lock
etcd::Response resp5 = etcd.unlock(resp1.lock_key()).get();
CHECK("unlock" == resp5.action());
REQUIRE(resp5.is_ok());
REQUIRE(0 == resp5.error_code());
}
TEST_CASE("lock using lease") {
etcd::Client etcd(etcd_url);
bool failed = false; bool failed = false;
std::function<void (std::exception_ptr)> handler = [&failed](std::exception_ptr eptr) { #ifndef _ETCD_NO_EXCEPTIONS
try { std::function<void(std::exception_ptr)> handler =
if (eptr) { [&failed](std::exception_ptr eptr) {
try {
if (eptr) {
std::rethrow_exception(eptr); std::rethrow_exception(eptr);
}
} catch (const std::exception& e) {
std::cerr << "Caught exception \"" << e.what() << "\"\n";
failed = true;
} }
} catch(const std::exception& e) { };
std::cerr << "Caught exception \"" << e.what() << "\"\n"; #else
failed = true; std::function<void(std::exception_ptr)> handler;
} #endif
};
// with handler // with handler
{ {
@ -166,24 +200,61 @@ TEST_CASE("lock using lease")
} }
} }
TEST_CASE("concurrent lock & unlock") TEST_CASE("concurrent lock & unlock") {
{ etcd::Client etcd(etcd_url);
etcd::Client etcd("http://127.0.0.1:2379");
std::string const lock_key = "/test/test_key"; std::string const lock_key = "/test/test_key";
constexpr size_t trials = 128; constexpr size_t trials = 192;
std::function<void(std::string const &, const size_t)> locker = [&etcd](std::string const &key, const size_t index) { std::function<void(std::string const&, const size_t)> locker =
std::cout << "start lock for " << index << std::endl; [&etcd](std::string const& key, const size_t index) {
auto resp = etcd.lock(key).get(); std::cout << "start lock for " << key << ", index is " << index
std::cout << "lock for " << index << " is ok, start sleep: ..." << resp.error_message() << std::endl; << std::endl;
REQUIRE(resp.is_ok()); auto resp = etcd.lock(key).get();
std::srand(index); std::cout << "lock for " << index << " is ok, starts sleeping: ..."
size_t time_to_sleep = 1; << resp.error_message() << std::endl
std::this_thread::sleep_for(std::chrono::seconds(time_to_sleep)); << std::flush;
REQUIRE(etcd.unlock(resp.lock_key()).get().is_ok()); REQUIRE(resp.is_ok());
std::cout << "thread " << index << " been unlocked" << std::endl; std::srand(index);
}; size_t time_to_sleep = 1;
std::this_thread::sleep_for(std::chrono::seconds(time_to_sleep));
std::cout << "lock for " << index << " resumes from sleep: ..."
<< resp.error_message() << std::endl
<< std::flush;
REQUIRE(etcd.unlock(resp.lock_key()).get().is_ok());
std::cout << "thread " << index << " been unlocked" << std::endl
<< std::flush;
};
std::vector<std::thread> locks(trials);
for (size_t index = 0; index < trials; ++index) {
locks[index] = std::thread(locker, lock_key, index);
}
for (size_t index = 0; index < trials; ++index) {
locks[index].join();
}
}
TEST_CASE("concurrent lock & unlock with a put in between") {
etcd::Client etcd(etcd_url);
std::string const lock_key = "/test/test_key";
constexpr size_t trials = 128;
std::function<void(std::string const&, const size_t)> locker =
[&etcd](std::string const& key, const size_t index) {
std::cout << "start lock for " << index << std::endl;
auto resp = etcd.lock(key, true).get();
std::cout << "lock for " << index << " is ok, start put and unlock: ..."
<< resp.error_message() << std::endl;
REQUIRE(resp.is_ok());
auto put_resp =
etcd.put("/test/test_put", "hello" + std::to_string(index)).get();
REQUIRE(put_resp.is_ok());
REQUIRE(etcd.unlock(resp.lock_key()).get().is_ok());
std::cout << "thread " << index << " been unlocked" << std::endl;
};
std::vector<std::thread> locks(trials); std::vector<std::thread> locks(trials);
for (size_t index = 0; index < trials; ++index) { for (size_t index = 0; index < trials; ++index) {

85
tst/MemLeakTest.cpp Normal file
View File

@ -0,0 +1,85 @@
// See also: https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/issues/86
#include <future>
#include <memory>
#include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
class DistributedLock {
public:
DistributedLock(const std::string& lock_name, uint timeout = 0);
~DistributedLock() noexcept;
inline bool lock_acquired() { return _acquired; }
private:
bool _acquired = false;
std::string _lock_key;
std::unique_ptr<::etcd::Client> _etcd_client;
};
DistributedLock::DistributedLock(const std::string& lock_name, uint timeout) {
_etcd_client = std::unique_ptr<etcd::Client>(new etcd::Client(etcd_url));
if (timeout == 0) {
etcd::Response resp = _etcd_client->lock(lock_name).get();
if (resp.is_ok()) {
_lock_key = resp.lock_key();
_acquired = true;
}
} else {
std::future<etcd::Response> future = std::async(std::launch::async, [&]() {
etcd::Response resp = _etcd_client->lock(lock_name).get();
return resp;
});
std::future_status status = future.wait_for(std::chrono::seconds(timeout));
if (status == std::future_status::ready) {
auto resp = future.get();
if (resp.is_ok()) {
_lock_key = resp.lock_key();
_acquired = true;
}
} else if (status == std::future_status::timeout) {
std::cerr << "failed to acquire distributed because of lock timeout"
<< std::endl;
} else {
std::cerr << "failed to acquire distributed lock" << std::endl;
}
}
}
DistributedLock::~DistributedLock() noexcept {
if (!_acquired) {
return;
}
auto resp = _etcd_client->unlock(_lock_key).get();
if (!resp.is_ok()) {
std::cout << resp.error_code() << std::endl;
}
}
int main() {
int i = 0, t = 0;
while (t < 100 /* update this value to make it run for longer */) {
{
DistributedLock lock(std::to_string(i), 0);
if (!lock.lock_acquired()) {
std::cerr << "failed to acquire lock" << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
++i;
++t;
if (i == 10) {
i = 0;
}
if (t % 10 == 0) {
std::cout << "round: i = " << i << ", t = " << t << std::endl;
}
}
}

View File

@ -0,0 +1,60 @@
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include <future>
#include <memory>
#include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp"
#include "etcd/Watcher.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
static std::atomic_int watcher_called;
void print_response(etcd::Response const& resp) { watcher_called.fetch_add(1); }
/**
* @brief emulate the behavior of creating watcher many times:
*
* 1. create a watcher
* 2. change a value
* 3. cancel the watcher
*/
void watch_once(etcd::Client& client, std::unique_ptr<etcd::Watcher>& watcher,
const size_t round) {
const std::string my_prefix = "/test";
const std::string my_key = my_prefix + "/foo";
watcher.reset(new etcd::Watcher(client, my_prefix, print_response, true));
int k = watcher_called.load();
client.set(my_key, "bar-" + std::to_string(round)).wait();
while (true) {
if (watcher_called.load() > k) {
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// cancel the watcher
watcher->Cancel();
}
TEST_CASE("watch shouldn't leak memory") {
watcher_called.store(0);
// issue some changes to see if the watcher works
etcd::Client client(etcd_url);
std::unique_ptr<etcd::Watcher> watcher;
for (int round = 0;
round < 10 /* update this value to make it run for longer */; ++round) {
if (round % 50 == 0) {
std::cout << "starting round " << round << std::endl;
}
watch_once(client, watcher, round);
}
std::cout << "watcher been called for " << watcher_called.load() << " times"
<< std::endl;
}

133
tst/RewatchTest.cpp Normal file
View File

@ -0,0 +1,133 @@
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include <chrono>
#include <thread>
#include "etcd/Client.hpp"
#include "etcd/SyncClient.hpp"
#include "etcd/Watcher.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
static int watcher_called = 0;
void print_response(etcd::Response const& resp) {
++watcher_called;
std::cout << "print response called" << std::endl;
if (resp.error_code()) {
std::cout << resp.error_code() << ": " << resp.error_message() << std::endl;
} else {
std::cout << resp.action() << " " << resp.value().as_string() << std::endl;
std::cout << "Previous value: " << resp.prev_value().as_string()
<< std::endl;
std::cout << "Events size: " << resp.events().size() << std::endl;
for (auto const& ev : resp.events()) {
std::cout << "Value change in events: "
<< static_cast<int>(ev.event_type())
<< ", prev kv = " << ev.prev_kv().key() << " -> "
<< ev.prev_kv().as_string() << ", kv = " << ev.kv().key()
<< " -> " << ev.kv().as_string() << std::endl;
}
}
}
void wait_for_connection(std::string endpoints) {
// wait until the client connects to etcd server
while (true) {
#ifndef _ETCD_NO_EXCEPTIONS
try {
etcd::Client client(endpoints);
if (client.head().get().is_ok()) {
break;
}
} catch (...) {
// pass
}
#else
etcd::Client client(endpoints);
if (client.head().get().is_ok()) {
break;
}
#endif
sleep(1);
}
}
void initialize_watcher(const std::string& endpoints, const std::string& prefix,
std::function<void(etcd::Response)> callback,
std::shared_ptr<etcd::Watcher>& watcher) {
// wait until the endpoints turn to be available
wait_for_connection(endpoints);
etcd::Client client(endpoints);
// 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);
});
}
TEST_CASE("watch should can be re-established") {
const std::string my_prefix = "/test";
// the watcher initialized in this way will auto re-connect to etcd
std::shared_ptr<etcd::Watcher> watcher;
initialize_watcher(etcd_url, my_prefix, print_response, watcher);
// issue some changes to see if the watcher works
for (int round = 0; round < 100000; ++round) {
#ifndef _ETCD_NO_EXCEPTIONS
try {
etcd::Client client(etcd_url);
auto response =
client.set(my_prefix + "/foo", "bar-" + std::to_string(round)).get();
} catch (...) {
// pass
}
#else
etcd::Client client(etcd_url);
auto response =
client.set(my_prefix + "/foo", "bar-" + std::to_string(round)).get();
#endif
std::this_thread::sleep_for(std::chrono::seconds(2));
}
// cancel the worker
watcher->Cancel();
// the watcher has been cancelled and shouldn't work anymore
for (int round = 10; round < 20; ++round) {
#ifndef _ETCD_NO_EXCEPTIONS
try {
etcd::Client client(etcd_url);
auto response =
client.set(my_prefix + "/foo", "bar-" + std::to_string(round)).get();
} catch (...) {
// pass
}
#else
etcd::Client client(etcd_url);
auto response =
client.set(my_prefix + "/foo", "bar-" + std::to_string(round)).get();
#endif
std::this_thread::sleep_for(std::chrono::seconds(2));
}
}

View File

@ -9,44 +9,50 @@ static std::string ca = "security-config/certs/ca.crt";
static std::string cert = "security-config/certs/etcd0.example.com.crt"; static std::string cert = "security-config/certs/etcd0.example.com.crt";
static std::string key = "security-config/private/etcd0.example.com.key"; static std::string key = "security-config/private/etcd0.example.com.key";
TEST_CASE("setup with auth") static const std::string etcd_url =
{ etcdv3::detail::resolve_etcd_endpoints("https://127.0.0.1:2379");
etcd::Client *etcd = etcd::Client::WithSSL("https://127.0.0.1:2379", ca, cert, key);
TEST_CASE("setup with auth") {
etcd::Client* etcd = etcd::Client::WithSSL(etcd_url, ca, cert, key);
etcd->rmdir("/test", true).wait(); etcd->rmdir("/test", true).wait();
} }
TEST_CASE("add a new key after authenticate") TEST_CASE("add a new key after authenticate") {
{ etcd::Client* etcd = etcd::Client::WithSSL(etcd_url, ca, cert, key);
etcd::Client *etcd = etcd::Client::WithSSL("https://127.0.0.1:2379", ca, cert, key);
etcd->rmdir("/test", true).wait(); etcd->rmdir("/test", true).wait();
etcd::Response resp = etcd->add("/test/key1", "42").get(); etcd::Response resp = etcd->add("/test/key1", "42").get();
REQUIRE(0 == resp.error_code()); REQUIRE(0 == resp.error_code());
CHECK("create" == resp.action()); CHECK("create" == resp.action());
etcd::Value const & val = resp.value(); etcd::Value const& val = resp.value();
CHECK("42" == val.as_string()); CHECK("42" == val.as_string());
CHECK("/test/key1" == val.key()); CHECK("/test/key1" == val.key());
CHECK(!val.is_dir()); CHECK(!val.is_dir());
CHECK(0 < val.created_index()); CHECK(0 < val.created_index());
CHECK(0 < val.modified_index()); CHECK(0 < val.modified_index());
CHECK(1 == val.version());
CHECK(0 < resp.index()); CHECK(0 < resp.index());
CHECK(105 == etcd->add("/test/key1", "43").get().error_code()); // Key already exists CHECK(
CHECK(105 == etcd->add("/test/key1", "42").get().error_code()); // Key already exists etcd::ERROR_KEY_ALREADY_EXISTS ==
CHECK("Key already exists" == etcd->add("/test/key1", "42").get().error_message()); etcd->add("/test/key1", "43").get().error_code()); // Key already exists
CHECK(
etcd::ERROR_KEY_ALREADY_EXISTS ==
etcd->add("/test/key1", "42").get().error_code()); // Key already exists
CHECK("etcd-cpp-apiv3: key already exists" ==
etcd->add("/test/key1", "42").get().error_message());
} }
TEST_CASE("read a value from etcd") TEST_CASE("read a value from etcd") {
{ etcd::Client* etcd = etcd::Client::WithSSL(etcd_url, ca, cert, key);
etcd::Client *etcd = etcd::Client::WithSSL("https://127.0.0.1:2379", ca, cert, key);
etcd::Response resp = etcd->get("/test/key1").get(); etcd::Response resp = etcd->get("/test/key1").get();
CHECK("get" == resp.action()); CHECK("get" == resp.action());
REQUIRE(resp.is_ok()); REQUIRE(resp.is_ok());
REQUIRE(0 == resp.error_code()); REQUIRE(0 == resp.error_code());
CHECK("42" == resp.value().as_string()); CHECK("42" == resp.value().as_string());
CHECK("" == etcd->get("/test").get().value().as_string()); // key points to a directory CHECK("" == etcd->get("/test").get().value().as_string()); // key points to a
// directory
} }
TEST_CASE("cleanup") TEST_CASE("cleanup") {
{ etcd::Client* etcd = etcd::Client::WithSSL(etcd_url, ca, cert, key);
etcd::Client *etcd = etcd::Client::WithSSL("https://127.0.0.1:2379", ca, cert, key);
REQUIRE(0 == etcd->rmdir("/test", true).get().error_code()); REQUIRE(0 == etcd->rmdir("/test", true).get().error_code());
} }

130
tst/TransactionTest.cpp Normal file
View File

@ -0,0 +1,130 @@
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
#include <chrono>
#include <iostream>
#include <thread>
#include "etcd/Client.hpp"
#include "etcd/v3/Transaction.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
TEST_CASE("setup") {
etcd::Client etcd(etcd_url);
etcd.rmdir("/test", true).wait();
}
TEST_CASE("add a new key") {
etcd::Client etcd(etcd_url);
etcd.rmdir("/test", true).wait();
// do some put using txn
{
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();
REQUIRE(0 == resp.error_code());
}
// check the value
{
etcd::Response resp = etcd.get("/test/x1").get();
REQUIRE(0 == resp.error_code());
CHECK(resp.value().as_string() == "1");
resp = etcd.get("/test/x2").get();
REQUIRE(0 == resp.error_code());
CHECK(resp.value().as_string() == "2");
resp = etcd.get("/test/x3").get();
REQUIRE(0 == resp.error_code());
CHECK(resp.value().as_string() == "3");
}
// do some put and delete using txn
{
etcdv3::Transaction txn;
// setup the conditions
txn.add_compare_value("/test/x1", "1");
txn.add_compare_value("/test/x2", "2");
txn.setup_put("/test/x1", "111");
txn.setup_delete("/test/x2");
txn.setup_delete("/test/x3");
txn.setup_put("/test/x4", "4");
etcd::Response resp = etcd.txn(txn).get();
REQUIRE(0 == resp.error_code());
}
// check the value
{
etcd::Response resp = etcd.get("/test/x1").get();
REQUIRE(0 == resp.error_code());
CHECK(resp.value().as_string() == "111");
resp = etcd.get("/test/x2").get();
REQUIRE(etcdv3::ERROR_KEY_NOT_FOUND == resp.error_code());
resp = etcd.get("/test/x3").get();
REQUIRE(etcdv3::ERROR_KEY_NOT_FOUND == resp.error_code());
resp = etcd.get("/test/x4").get();
REQUIRE(0 == resp.error_code());
CHECK(resp.value().as_string() == "4");
}
}
TEST_CASE("fetch & add") {
etcd::Client etcd(etcd_url);
etcd.rmdir("/test", true).wait();
etcd.set("/test/key", "0").wait();
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());
}
};
// run 1000 times
const size_t rounds = 100;
std::atomic_size_t counter(0);
std::vector<std::thread> threads;
for (size_t i = 0; i < 10; ++i) {
threads.emplace_back([&]() {
while (counter.fetch_add(1) < rounds) {
fetch_and_add(etcd, "/test/key");
}
});
}
for (auto& thr : threads) {
thr.join();
}
// check the value
{
etcd::Response resp = etcd.get("/test/key").get();
REQUIRE(0 == resp.error_code());
CHECK(resp.value().as_string() == std::to_string(rounds));
}
}
TEST_CASE("cleanup") {
etcd::Client etcd(etcd_url);
REQUIRE(0 == etcd.rmdir("/test", true).get().error_code());
}

View File

@ -4,116 +4,231 @@
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include "etcd/Watcher.hpp"
#include "etcd/SyncClient.hpp" #include "etcd/SyncClient.hpp"
#include "etcd/Watcher.hpp"
static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
static std::string etcd_uri("http://127.0.0.1:2379");
static int watcher_called = 0; static int watcher_called = 0;
void printResponse(etcd::Response const & resp) void printResponse(etcd::Response const& resp) {
{
++watcher_called;
std::cout << "print response called" << std::endl;
if (resp.error_code()) { if (resp.error_code()) {
std::cout << resp.error_code() << ": " << resp.error_message() << std::endl; std::cout << "Watcher " << resp.watch_id() << " fails with "
} << resp.error_code() << ": " << resp.error_message() << std::endl;
else } else {
{ std::cout << "Watcher " << resp.watch_id() << " responses with "
std::cout << resp.action() << " " << resp.value().as_string() << std::endl; << resp.action() << " " << resp.value().as_string() << std::endl;
std::cout << "Previous value: " << resp.prev_value().as_string() << std::endl; std::cout << "Previous value: " << resp.prev_value().as_string()
<< std::endl;
std::cout << "Events size: " << resp.events().size() << std::endl;
for (auto const& ev : resp.events()) {
if (ev.prev_kv().key().find("/leader") == 0 ||
ev.kv().key().find("/leader") == 0) {
return;
}
std::cout << "Value change in events: "
<< static_cast<int>(ev.event_type())
<< ", prev kv = " << ev.prev_kv().key() << " -> "
<< ev.prev_kv().as_string() << ", kv = " << ev.kv().key()
<< " -> " << ev.kv().as_string() << std::endl;
}
} }
std::cout << "print response called" << std::endl;
++watcher_called;
} }
TEST_CASE("create watcher with cancel") TEST_CASE("create watcher") {
{ etcd::SyncClient etcd(etcd_url);
etcd::SyncClient etcd(etcd_uri);
etcd.rmdir("/test", true); etcd.rmdir("/test", true);
watcher_called = 0; watcher_called = 0;
etcd::Watcher watcher(etcd_uri, "/test", printResponse, true); {
std::this_thread::sleep_for(std::chrono::seconds(3)); etcd::Watcher watcher(etcd_url, "/test", printResponse, true);
std::this_thread::sleep_for(std::chrono::seconds(5));
etcd.set("/test/key", "42");
std::this_thread::sleep_for(std::chrono::seconds(5));
etcd.set("/test/key", "43");
std::this_thread::sleep_for(std::chrono::seconds(5));
}
CHECK(2 == watcher_called);
etcd.rmdir("/test", true);
}
TEST_CASE("watch with correct prefix") {
etcd::SyncClient etcd(etcd_url);
etcd.rmdir("/test", true);
watcher_called = 0;
etcd::Watcher watcher(etcd_url, "/test/key_prefix", printResponse, true);
{
etcd.set("/test/key1", "42");
std::this_thread::sleep_for(std::chrono::seconds(5));
CHECK(0 == watcher_called);
}
{
etcd.set("/test/key_prefix", "42");
std::this_thread::sleep_for(std::chrono::seconds(5));
CHECK(1 == watcher_called);
}
{
etcd.set("/test/key_prefix1", "42");
std::this_thread::sleep_for(std::chrono::seconds(5));
CHECK(2 == watcher_called);
}
{
etcd.set("/test/key_prefiy", "42");
std::this_thread::sleep_for(std::chrono::seconds(5));
CHECK(2 == watcher_called);
}
{
etcd.set("/test/key1", "42");
std::this_thread::sleep_for(std::chrono::seconds(5));
CHECK(2 == watcher_called);
}
etcd.rmdir("/test", true);
}
TEST_CASE("create watcher with cancel") {
etcd::SyncClient etcd(etcd_url);
etcd.rmdir("/test", true);
watcher_called = 0;
etcd::Watcher watcher(etcd_url, "/test", printResponse, true);
std::this_thread::sleep_for(std::chrono::seconds(5));
etcd.set("/test/key", "42"); etcd.set("/test/key", "42");
etcd.set("/test/key", "43"); etcd.set("/test/key", "43");
etcd.rm("/test/key"); etcd.rm("/test/key");
etcd.set("/test/key", "44"); etcd.set("/test/key", "44");
std::this_thread::sleep_for(std::chrono::seconds(3)); std::this_thread::sleep_for(std::chrono::seconds(5));
CHECK(4 == watcher_called); CHECK(4 == watcher_called);
watcher.Cancel(); watcher.Cancel();
etcd.set("/test/key", "50"); etcd.set("/test/key", "50");
etcd.set("/test/key", "51"); etcd.set("/test/key", "51");
std::this_thread::sleep_for(std::chrono::seconds(3)); std::this_thread::sleep_for(std::chrono::seconds(5));
CHECK(4 == watcher_called); CHECK(4 == watcher_called);
etcd.rmdir("/test", true); etcd.rmdir("/test", true);
} }
TEST_CASE("create watcher on ranges with cancel") TEST_CASE("create watcher on ranges with cancel") {
{ etcd::SyncClient etcd(etcd_url);
etcd::SyncClient etcd(etcd_uri);
etcd.rmdir("/test", true); etcd.rmdir("/test", true);
watcher_called = 0; watcher_called = 0;
etcd::Watcher watcher(etcd_uri, "/test/key1", "/test/key5", printResponse); etcd::Watcher watcher(etcd_url, "/test/key1", "/test/key5", printResponse);
std::this_thread::sleep_for(std::chrono::seconds(3)); std::this_thread::sleep_for(std::chrono::seconds(5));
etcd.set("/test/key1", "42"); etcd.set("/test/key1", "42");
etcd.set("/test/key2", "43"); etcd.set("/test/key2", "43");
etcd.rm("/test/key1"); etcd.rm("/test/key1");
etcd.set("/test/key5", "44"); etcd.set("/test/key5", "44");
std::this_thread::sleep_for(std::chrono::seconds(3)); std::this_thread::sleep_for(std::chrono::seconds(5));
CHECK(3 == watcher_called); CHECK(3 == watcher_called);
watcher.Cancel(); watcher.Cancel();
etcd.set("/test/key3", "50"); etcd.set("/test/key3", "50");
etcd.set("/test/key4", "51"); etcd.set("/test/key4", "51");
std::this_thread::sleep_for(std::chrono::seconds(3)); std::this_thread::sleep_for(std::chrono::seconds(5));
CHECK(3 == watcher_called); CHECK(3 == watcher_called);
etcd.rmdir("/test", true); etcd.rmdir("/test", true);
} }
TEST_CASE("create watcher") TEST_CASE("watch should exit normally") {
{ // cancel immediately after start watch.
etcd::SyncClient etcd(etcd_uri); etcd::Watcher watcher(etcd_url, "/test", printResponse, true);
etcd.rmdir("/test", true);
watcher_called = 0;
{
etcd::Watcher watcher(etcd_uri, "/test", printResponse, true);
std::this_thread::sleep_for(std::chrono::seconds(3));
etcd.set("/test/key", "42");
std::this_thread::sleep_for(std::chrono::seconds(3));
etcd.set("/test/key", "43");
std::this_thread::sleep_for(std::chrono::seconds(3));
}
CHECK(2 == watcher_called);
etcd.rmdir("/test", true).error_code();
}
TEST_CASE("watch should exit normally")
{
// cancal immediately after start watch.
etcd::Watcher watcher(etcd_uri, "/test", printResponse, true);
watcher.Cancel(); watcher.Cancel();
} }
TEST_CASE("watch should can be cancelled repeatedly") TEST_CASE("watch should can be cancelled repeatedly") {
{ etcd::Watcher watcher(etcd_url, "/test", printResponse, true);
// cancal immediately after start watch.
etcd::Watcher watcher(etcd_uri, "/test", printResponse, true);
std::vector<std::thread> threads(10); std::vector<std::thread> threads(10);
for (size_t i = 0; i < 10; ++i) { for (size_t i = 0; i < 10; ++i) {
threads[i] = std::thread([&]() { threads[i] = std::thread([&]() { watcher.Cancel(); });
watcher.Cancel();
});
} }
for (size_t i = 0; i < 10; ++i) { for (size_t i = 0; i < 10; ++i) {
threads[i].join(); threads[i].join();
} }
} }
TEST_CASE("watch changes on the same key (#212)") {
std::string key_watch = "key watch";
etcd::SyncClient client(etcd_url);
client.put(key_watch, "inittt");
auto current_index = client.head().index();
std::cout << "Current index " << current_index << std::endl;
auto internal_cb = [&](etcd::Response resp) -> void {
if (!resp.is_ok()) {
std::cout << "Error: " << resp.error_message() << std::endl;
return;
}
for (auto const& event : resp.events()) {
std::cout << "Watch '" << event.kv().key()
<< "'. ModifedRevision : " << event.kv().modified_index()
<< "', Vision : " << event.kv().version()
<< ", value = " << event.kv().as_string() << std::endl;
}
};
auto wait_cb = [&](bool) {};
etcd::Watcher w(client, key_watch, current_index, std::move(internal_cb),
std::move(wait_cb), false);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
for (int i = 0; i < 10; ++i) {
std::string value = "watch_" + std::to_string(i);
client.put(key_watch, value);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
TEST_CASE("create two watcher") {
etcd::Watcher w1(etcd_url, "/test", printResponse, true);
etcd::Watcher w2(etcd_url, "/test", printResponse, true);
std::this_thread::sleep_for(std::chrono::seconds(5));
}
TEST_CASE("using two watcher") {
etcd::SyncClient etcd(etcd_url);
int watched1 = 0;
int watched2 = 0;
etcd::Watcher w1(
etcd, "/test/def",
[&](etcd::Response const& resp) {
std::cout << "w1 called: " << resp.events().at(0).event_type() << " on "
<< resp.events().at(0).kv().key() << std::endl;
++watched1;
},
true);
etcd::Watcher w2(
etcd, "/test",
[&](etcd::Response const& resp) {
std::cout << "w2 called: " << resp.events().at(0).event_type() << " on "
<< resp.events().at(0).kv().key() << std::endl;
++watched2;
},
true);
std::this_thread::sleep_for(std::chrono::seconds(5));
etcd.put("/test/def/xxx", "42");
etcd.put("/test/abc", "42");
etcd.rm("/test/def/xxx");
etcd.rm("/test/abc");
std::this_thread::sleep_for(std::chrono::seconds(5));
CHECK(2 == watched1);
CHECK(4 == watched2);
}
// TEST_CASE("request cancellation") // TEST_CASE("request cancellation")
// { // {
// etcd::Client etcd(etcd_uri); // etcd::Client etcd(etcd_url);
// etcd.set("/test/key1", "42").wait(); // etcd.set("/test/key1", "42").wait();
// pplx::task<etcd::Response> res = etcd.watch("/test/key1"); // pplx::task<etcd::Response> res = etcd.watch("/test/key1");

View File

@ -1,6 +0,0 @@
Source: etcd-cpp-apiv3
Version: etcd-cpp-apiv3-7e280ec
Homepage: https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3
Description: The etcd-cpp-apiv3 is a C++ API for etcd's v3 client API, i.e., ETCDCTL_API=3.
Build-Depends: boost-system, boost-thread, boost-random,
cpprestsdk, grpc, openssl, protobuf

View File

@ -1,25 +0,0 @@
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO etcd-cpp-apiv3/etcd-cpp-apiv3
REF 7e280ec8a49aaf26976d72a4080f2e2c6756f2b7
SHA512 1e1d525f79840731102e6400ff2582807e0626b58fa60726e49bea6ca51868a6b8bf3a34c7bf1d1266e156daa1e741bbfa2acce42b54680a23426bc00192dc6d
HEAD_REF master
)
vcpkg_configure_cmake(
SOURCE_PATH ${SOURCE_PATH}
PREFER_NINJA
OPTIONS
-DBUILD_ETCD_TESTS=OFF
)
set(VCPKG_POLICY_DLLS_WITHOUT_LIBS enabled)
set(VCPKG_POLICY_DLLS_WITHOUT_EXPORTS enabled)
vcpkg_install_cmake()
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)
vcpkg_copy_pdbs()
# Handle copyright
file(INSTALL ${SOURCE_PATH}/LICENSE.txt DESTINATION ${CURRENT_PACKAGES_DIR}/share/etcd-cpp-apiv3 RENAME copyright)