Compare commits

...

56 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
97 changed files with 8158 additions and 6565 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

View File

@ -6,21 +6,18 @@ concurrency:
group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }} group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }}
cancel-in-progress: true 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:
os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04, macos-10.15, macos-11, macos-12] os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
etcd: [v3.2.26, v3.3.11, v3.4.13, v3.5.7] etcd: [v3.2.32, v3.3.27, v3.4.27, v3.5.9]
exclude: exclude:
- os: ubuntu-18.04
etcd: v3.3.11
- os: ubuntu-18.04
etcd: v3.4.13
- os: ubuntu-18.04
etcd: v3.5.7
- os: ubuntu-20.04 - os: ubuntu-20.04
etcd: v3.2.26 etcd: v3.2.26
- os: ubuntu-20.04 - os: ubuntu-20.04
@ -35,13 +32,6 @@ jobs:
- os: ubuntu-22.04 - os: ubuntu-22.04
etcd: v3.5.7 etcd: v3.5.7
- os: macos-10.15
etcd: v3.2.26
- os: macos-10.15
etcd: v3.3.11
- os: macos-10.15
etcd: v3.4.13
- os: macos-11 - os: macos-11
etcd: v3.2.26 etcd: v3.2.26
- os: macos-11 - os: macos-11
@ -92,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: |
@ -129,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: |
@ -172,7 +170,11 @@ 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:/usr/local/lib/x86_64-linux-gnu export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib:/usr/local/lib/x86_64-linux-gnu
@ -184,6 +186,42 @@ jobs:
-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
@ -205,24 +243,40 @@ jobs:
# 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 ........................." echo "Run the etcd sync test ........................."
./build/bin/EtcdSyncTest ./build/bin/EtcdSyncTest
echo "Run the etcd test ........................." echo "Run the etcd test ........................."
./build/bin/EtcdTest ./build/bin/EtcdTest
echo "Run the etcd campaign test ........................."
./build/bin/CampaignTest
echo "Run the etcd memory leak test ........................." echo "Run the etcd memory leak test ........................."
./build/bin/MemLeakTest ./build/bin/MemLeakTest
echo "Run the etcd watcher test ........................." echo "Run the etcd watcher test ........................."
./build/bin/WatcherTest ./build/bin/WatcherTest
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 ........................." echo "Run the etcd transaction test ........................."
./build/bin/TransactionTest ./build/bin/TransactionTest
echo "Run the etcd election test ........................." echo "Run the etcd election test ........................."
./build/bin/ElectionTest ./build/bin/ElectionTest
echo "Run the etcd with fork test ........................."
./build/bin/ForkTest
killall -TERM etcd || true killall -TERM etcd || true
sleep 5 sleep 5
@ -349,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

View File

@ -16,10 +16,6 @@ jobs:
os: [ubuntu-20.04] os: [ubuntu-20.04]
etcd: [v3.4.13] etcd: [v3.4.13]
steps: steps:
- name: Get time
run: |
date +'%Y-%m' > snapshot.txt
- name: Install dependencies for Linux - name: Install dependencies for Linux
run: | run: |
# switch to centos stream # switch to centos stream
@ -44,15 +40,11 @@ jobs:
with: with:
submodules: true submodules: true
- name: Get time
run: |
date +'%Y-%m' > snapshot.txt
- name: Cache for cccahe - name: Cache for cccahe
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: /home/runner/.ccache path: /home/runner/.ccache
key: ${{ runner.os }}-centos-ccache-${{ hashFiles('**/snapshot.txt') }} key: ${{ runner.os }}-centos-ccache-${{ hashFiles('/CMakeLists.txt') }}
restore-keys: | restore-keys: |
${{ runner.os }}-centos-ccache- ${{ runner.os }}-centos-ccache-

View File

@ -4,8 +4,8 @@ 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 11) 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)
@ -26,10 +26,22 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
) )
endif() endif()
option(BUILD_WITH_NO_EXCEPTIONS "Build etcd-cpp-apiv3 with disabling exception handling, i.e., -fno-exceptions" OFF)
option(BUILD_SHARED_LIBS "Build etcd-cpp-apiv3 shared libraries" ON) 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(BUILD_ETCD_TESTS "Build etcd-cpp-apiv3 test cases" OFF)
option(CMAKE_POSITION_INDEPENDENT_CODE "Build etcd-cpp-apiv3 with -fPIC" ON) 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_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)
@ -60,22 +72,38 @@ if(NOT (CMAKE_CXX_COMPILER_LAUNCHER MATCHES "ccache") AND NOT (CMAKE_C_COMPILER_
endif(ccache_EXECUTABLE) endif(ccache_EXECUTABLE)
endif() endif()
macro(use_cxx11 target) macro(use_cxx target)
if(CMAKE_VERSION VERSION_LESS "3.1") if(CMAKE_VERSION VERSION_LESS "3.1")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${target} PRIVATE "-std=gnu++11") target_compile_options(${target} PRIVATE "-std=gnu++${ETCD_CMAKE_CXX_STANDARD}")
else() else()
target_compile_options(${target} PRIVATE "-std=c++11") target_compile_options(${target} PRIVATE "-std=c++${ETCD_CMAKE_CXX_STANDARD}")
endif() endif()
else() elseif(CMAKE_VERSION VERSION_LESS "3.8")
set_target_properties(${target} PROPERTIES set_target_properties(${target} PROPERTIES
CXX_STANDARD 11 CXX_STANDARD ${ETCD_CMAKE_CXX_STANDARD}
CXX_STANDARD_REQUIRED ON CXX_STANDARD_REQUIRED ON
) )
else()
target_compile_features(${target} PUBLIC cxx_std_${ETCD_CMAKE_CXX_STANDARD})
endif() endif()
endmacro(use_cxx11) 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)
find_package(Boost REQUIRED COMPONENTS system thread random)
if(APPLE) 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)
@ -94,7 +122,10 @@ if(APPLE)
endif() endif()
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
find_package(Protobuf CONFIG QUIET)
if (NOT Protobuf_FOUND)
find_package(Protobuf REQUIRED) find_package(Protobuf REQUIRED)
endif()
if(Protobuf_PROTOC_EXECUTABLE) if(Protobuf_PROTOC_EXECUTABLE)
if(NOT TARGET protobuf::protoc) if(NOT TARGET protobuf::protoc)
add_executable(protobuf::protoc IMPORTED) add_executable(protobuf::protoc IMPORTED)
@ -104,25 +135,41 @@ if(Protobuf_PROTOC_EXECUTABLE)
endif() endif()
endif() endif()
endif() endif()
find_package(cpprestsdk QUIET)
if(cpprestsdk_FOUND) # When cross compiling we look for the native protoc compiler
set(CPPREST_INCLUDE_DIR) # overwrite protobuf::protoc with the proper protoc
set(CPPREST_LIB cpprestsdk::cpprest) if(CMAKE_CROSSCOMPILING)
else() find_program(Protobuf_PROTOC_EXECUTABLE REQUIRED NAMES protoc)
find_library(CPPREST_LIB NAMES cpprest) if(NOT TARGET protobuf::protoc)
find_path(CPPREST_INCLUDE_DIR NAMES cpprest/http_client.h) 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)
set(GRPC_LIBRARIES gRPC::gpr gRPC::grpc 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) 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++)
get_target_property(GRPC_INCLUDE_DIR gRPC::grpc INTERFACE_INCLUDE_DIRECTORIES) 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("-- Found GRPC: ${GRPC_LIBRARIES} (found version: \"${gRPC_VERSION}\")") 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 # 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") 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 " message(FATAL_ERROR "gRPC '${gRPC_VERSION}' is not supported, please install a newer gRPC library "
@ -135,13 +182,36 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenerateProtobufGRPC.cmake)
if(gRPC_VERSION VERSION_LESS "1.21" OR gRPC_VERSION VERSION_GREATER "1.31") if(gRPC_VERSION VERSION_LESS "1.21" OR gRPC_VERSION VERSION_GREATER "1.31")
add_definitions(-DWITH_GRPC_CHANNEL_CLASS) add_definitions(-DWITH_GRPC_CHANNEL_CLASS)
endif() 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} ${GRPC_INCLUDE_DIR}
${OPENSSL_INCLUDE_DIR}) ${OPENSSL_INCLUDE_DIR})
@ -154,9 +224,13 @@ if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
endif() endif()
check_cxx_compiler_flag(-Wno-c++17-extensions W_NO_CPP17_EXTENSIONS) 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) if(W_NO_CPP17_EXTENSIONS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++17-extensions") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++17-extensions")
endif() endif()
if(W_NO_EXTRA_SEMI)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-extra-semi")
endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
@ -166,15 +240,19 @@ if(BUILD_ETCD_TESTS)
add_subdirectory(tst) add_subdirectory(tst)
endif() endif()
install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/Client.hpp install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/KeepAlive.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/KeepAlive.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/SyncClient.hpp ${CMAKE_CURRENT_SOURCE_DIR}/etcd/SyncClient.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/Response.hpp ${CMAKE_CURRENT_SOURCE_DIR}/etcd/Response.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/Value.hpp ${CMAKE_CURRENT_SOURCE_DIR}/etcd/Value.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/Watcher.hpp ${CMAKE_CURRENT_SOURCE_DIR}/etcd/Watcher.hpp
DESTINATION include/etcd) DESTINATION include/etcd)
if(NOT BUILD_ETCD_CORE_ONLY)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/Client.hpp
DESTINATION include/etcd)
endif()
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/action_constants.hpp install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/action_constants.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/Transaction.hpp ${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/Transaction.hpp
${CMAKE_CURRENT_SOURCE_DIR}/etcd/v3/Member.hpp
DESTINATION include/etcd/v3) DESTINATION include/etcd/v3)
configure_file(etcd-cpp-api-config.in.cmake configure_file(etcd-cpp-api-config.in.cmake
@ -193,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})
@ -238,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/")
@ -249,14 +338,15 @@ set(CPACK_DEBIAN_PACKAGE_INSTALL "/usr/lib/lib*.so*"
"/usr/include/etcd/*.hpp" "/usr/include/etcd/*.hpp"
"/usr/include/etcd/v3/action_constants.hpp" "/usr/include/etcd/v3/action_constants.hpp"
"/usr/include/etcd/v3/Transaction.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")
find_program(DEBUILD_EXECUTABLE debuild) find_program(DEBUILD_EXECUTABLE debuild)
find_program(DPUT_EXECUTABLE dput) find_program(DPUT_EXECUTABLE dput)

143
README.md
View File

@ -18,7 +18,7 @@ i.e., `ETCDCTL_API=3`.
- 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:
@ -28,7 +28,7 @@ i.e., `ETCDCTL_API=3`.
## Requirements ## Requirements
1. boost and openssl 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:
@ -51,7 +51,7 @@ i.e., `ETCDCTL_API=3`.
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] + 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)), 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` if the system-installed openssl is preferred, you need to add `-DgRPC_SSL_PROVIDER=package`
when building gRPC with CMake. when building gRPC with CMake.
@ -83,6 +83,36 @@ dependencies have been successfully installed:
cmake .. cmake ..
make -j$(nproc) && 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
@ -92,28 +122,27 @@ Github when you encounter problems when working with etcd 3.x releases.
There are various discussion about whether to support a user-transparent multi-thread executor in 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 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) for more discussion about [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 implementation of underlying thread model).
The _etcd-cpp-apiv3_ library supports both synchronous and asynchronous runtime, in two separate The _etcd-cpp-apiv3_ library supports both synchronous and asynchronous runtime, controlled by the
library as follows: cmake option `BUILD_ETCD_CORE_ONLY=ON/OFF` (defaults to `OFF`).
- **etcd-cpp-api-core**: the synchronous runtime, provides a blocking-style API and the users are responsible - When it is set as `OFF`: the library artifact name will be `libetcd-cpp-api.{a,so,dylib,lib,dll}` and a
for handling dispatch the request into separate thread to avoid been blocked by waiting for response. cmake target `etcd-cpp-api` is exported and pointed to it. The library provides both synchronous runtime
- target found by cmake: `ETCD_CPP_CORE_LIBRARIES` (`etcd/SyncClient.hpp`) and asynchronous runtime (`etcd/Client.hpp`), and the `cpprestsdk` is a
required dependency.
- **etcd-cpp-api**: the asynchronous runtime backed by the `pplx` library from - When it is set as `ON`: the library artifact name will be `libetcd-cpp-api-core.{a,so,dylib,lib,dll}`
[cpprestsdk](https://github.com/microsoft/cpprestsdk), where a `boost::asio::io_context` and a pool and a cmake target `etcd-cpp-api` is exported and pointed to it. The library provides only the
of threads is used to run the asynchronous operations in the background. By default the number of synchronous runtime (`etcd/SyncClient.hpp`), and the `cpprestsdk` won't be required.
threads in the thread pool equals to `std::thread::hardware_concurrency()`.
- target found by cmake: `ETCD_CPP_LIBRARIES`
We encourage the users to use the asynchronous runtime by default, as it provides more flexibility 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. However, please note and convenient APIs and less possibilities for errors that block the main thread. Note that the
that the asynchronous runtime will setup a thread pool in the background. asynchronous runtime requires `cpprestsdk` and will setup a thread pool in the background.
Note that `etcd-cpp-api-core` and `etcd-cpp-api` are two separate target and don't depends on each other. **Warning: users cannot link both `libetcd-cpp-api.{a,so,dylib,lib,dll}` and `libetcd-cpp-api-core.{a,so,dylib,lib,dll}`
You should depends on either of them in your program. to same program.**
## Usage ## Usage
@ -193,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.
@ -496,10 +533,11 @@ some specific conditions.
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
* `rm_if(key, old_value)` deletes an already existing value but only if the previous
value equals with old_value. If the values does not match returns with "Compare failed" error value equals with old_value. If the values does not match returns with "Compare failed" error
(code `ERROR_COMPARE_FAILED`) (code `ERROR_COMPARE_FAILED`)
* `rm_if(key, value, old_index)` deletes an already existing value but only if the index of * `rm_if(key, old_index)` deletes an already existing value but only if the index of
the previous value equals with old_index. If the indices does not match returns with "Compare the previous value equals with old_index. If the indices does not match returns with "Compare
failed" error (code `ERROR_COMPARE_FAILED`) failed" error (code `ERROR_COMPARE_FAILED`)
@ -524,13 +562,13 @@ keys defined by the prefix. mkdir method is removed since etcdv3 treats everythi
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
@ -759,7 +797,9 @@ std::shared_ptr<etcd::Watcher> watcher;
initialize_watcher(endpoints, prefix, callback, watcher); initialize_watcher(endpoints, prefix, callback, watcher);
``` ```
For a complete runnable example, see also [./tst/RewatchTest.cpp](./tst/RewatchTest.cpp). 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
@ -830,6 +870,10 @@ 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.
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 transactions
Etcd v3's [Transaction APIs](https://etcd.io/docs/v3.4/learning/api/#transaction) is supported via the Etcd v3's [Transaction APIs](https://etcd.io/docs/v3.4/learning/api/#transaction) is supported via the
@ -849,11 +893,32 @@ Transactions in etcd supports set a set of comparison targets to specify the con
etcdv3::Transaction txn; etcdv3::Transaction txn;
// setup the conditions // setup the conditions
txn.reset_key("/test/x1"); txn.add_compare_value("/test/x1", "1");
txn.init_compare("1", etcdv3::CompareResult::EQUAL, etcdv3::CompareTarget::VALUE); txn.add_compare_value("/test/x2", "2");
txn.reset_key("/test/x2"); // or, compare the last modified revision
txn.init_compare("2", etcdv3::CompareResult::EQUAL, etcdv3::CompareTarget::VALUE); 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), See full example of the usages of transaction APIs, please refer to [./tst/TransactionTest.cpp](./tst/TransactionTest.cpp),
@ -874,17 +939,21 @@ pplx::task<Response> proclaim(std::string const &name, int64_t lease_id,
pplx::task<Response> leader(std::string const &name); pplx::task<Response> leader(std::string const &name);
std::unique_ptr<Observer> observe(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, pplx::task<Response> resign(std::string const &name, int64_t lease_id,
std::string const &key, int64_t revision); 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` returned by `observe()` can be use to monitor the changes of election ownership.
The observer stream will be canceled when been destructed. The observer stream will be canceled when been destructed.
```c++ ```c++
std::unique_ptr<etcd::Observer> observer = etcd.observe("test"); std::unique_ptr<etcd::SyncClient::Observer> observer = etcd.observe("test");
// wait one change event, blocked execution // wait one change event, blocked execution
etcd::Response resp = observer->WaitOnce(); etcd::Response resp = observer->WaitOnce();
@ -899,9 +968,17 @@ The observer stream will be canceled when been destructed.
observer.reset(nullptr); observer.reset(nullptr);
``` ```
for more details, please refer to [etcd/Client.hpp](./etcd/Client.hpp). for more details, please refer to [etcd/Client.hpp](./etcd/Client.hpp) and [tst/ElectionTest.cpp](./tst/ElectionTest.cpp).
### TODO ## `-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)

View File

@ -22,50 +22,60 @@ 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)
if(NOT TARGET gRPC::gpr)
add_library(gRPC::gpr UNKNOWN IMPORTED) add_library(gRPC::gpr UNKNOWN IMPORTED)
set_target_properties(gRPC::gpr PROPERTIES set_target_properties(gRPC::gpr PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR} INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
INTERFACE_LINK_LIBRARIES "-lpthread;-ldl" INTERFACE_LINK_LIBRARIES "-lpthread;-ldl"
IMPORTED_LOCATION ${GPR_LIBRARY} 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)
if(NOT TARGET gRPC::grpc)
add_library(gRPC::grpc UNKNOWN IMPORTED) add_library(gRPC::grpc UNKNOWN IMPORTED)
set_target_properties(gRPC::grpc PROPERTIES set_target_properties(gRPC::grpc PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR} INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
INTERFACE_LINK_LIBRARIES gRPC::gpr INTERFACE_LINK_LIBRARIES gRPC::gpr
IMPORTED_LOCATION ${GRPC_LIBRARY} 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)
if(NOT TARGET gRPC::grpc++)
add_library(gRPC::grpc++ UNKNOWN IMPORTED) add_library(gRPC::grpc++ UNKNOWN IMPORTED)
set_target_properties(gRPC::grpc++ PROPERTIES set_target_properties(gRPC::grpc++ PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR} INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
INTERFACE_LINK_LIBRARIES gRPC::grpc INTERFACE_LINK_LIBRARIES gRPC::grpc
IMPORTED_LOCATION ${GRPC_GRPC++_LIBRARY} 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)
if(NOT TARGET gRPC::grpc++_reflection)
add_library(gRPC::grpc++_reflection UNKNOWN IMPORTED) add_library(gRPC::grpc++_reflection UNKNOWN IMPORTED)
set_target_properties(gRPC::grpc++_reflection PROPERTIES set_target_properties(gRPC::grpc++_reflection PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR} INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
INTERFACE_LINK_LIBRARIES gRPC::grpc++ INTERFACE_LINK_LIBRARIES gRPC::grpc++
IMPORTED_LOCATION ${GRPC_GRPC++_REFLECTION_LIBRARY} 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)
if(NOT TARGET gRPC::grpc_cpp_plugin)
add_executable(gRPC::grpc_cpp_plugin IMPORTED) add_executable(gRPC::grpc_cpp_plugin IMPORTED)
set_target_properties(gRPC::grpc_cpp_plugin PROPERTIES set_target_properties(gRPC::grpc_cpp_plugin PROPERTIES
IMPORTED_LOCATION ${GRPC_CPP_PLUGIN} IMPORTED_LOCATION ${GRPC_CPP_PLUGIN}
) )
endif()
file( file(
WRITE "${CMAKE_BINARY_DIR}/get_gRPC_version.cc" WRITE "${CMAKE_BINARY_DIR}/get_gRPC_version.cc"

View File

@ -32,5 +32,8 @@ 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}, \netcd-cpp-apiv3 core libraries: ${ETCD_CPP_CORE_LIBRARIES}\ninclude directories: ${ETCD_CPP_INCLUDE_DIRS}" "etcd-cpp-apiv3 version: @etcd-cpp-api_VERSION@\n"
"etcd-cpp-apiv3 libraries: ${ETCD_CPP_LIBRARIES}\n"
"etcd-cpp-apiv3 core libraries: ${ETCD_CPP_CORE_LIBRARIES}\n"
"include directories: ${ETCD_CPP_INCLUDE_DIRS}"
) )

View File

@ -14,24 +14,24 @@
#include "etcd/SyncClient.hpp" #include "etcd/SyncClient.hpp"
#include "etcd/v3/action_constants.hpp" #include "etcd/v3/action_constants.hpp"
namespace etcd namespace etcd {
{
/** /**
* Client is responsible for maintaining a connection towards an etcd server. * Client is responsible for maintaining a connection towards an etcd server.
* Etcd operations can be reached via the methods of the client. * Etcd operations can be reached via the methods of the client.
*/ */
class Client class Client {
{
public: public:
/** /**
* Constructs an async etcd client object from an established synchronous client. * Constructs an async etcd client object from an established synchronous
* client.
* *
* @param sync_client The synchronous client to use for the async client. * @param sync_client The synchronous client to use for the async client.
*/ */
Client(SyncClient* client); Client(SyncClient* client);
/** /**
* Constructs an async etcd client object from an established synchronous client. * Constructs an async etcd client object from an established synchronous
* client.
* *
* @param sync_client The synchronous client to use for the async client. * @param sync_client The synchronous client to use for the async client.
*/ */
@ -40,9 +40,10 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param load_balancer is the load balance strategy, can be one of
* round_robin/pick_first/grpclb/xds.
*/ */
Client(std::string const& etcd_url, Client(std::string const& etcd_url,
std::string const& load_balancer = "round_robin"); std::string const& load_balancer = "round_robin");
@ -50,8 +51,8 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
*/ */
Client(std::string const& etcd_url, Client(std::string const& etcd_url,
@ -65,9 +66,10 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param load_balancer is the load balance strategy, can be one of
* round_robin/pick_first/grpclb/xds.
*/ */
static Client* WithUrl(std::string const& etcd_url, static Client* WithUrl(std::string const& etcd_url,
std::string const& load_balancer = "round_robin"); std::string const& load_balancer = "round_robin");
@ -75,8 +77,8 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
*/ */
static Client* WithUrl(std::string const& etcd_url, static Client* WithUrl(std::string const& etcd_url,
@ -90,34 +92,32 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param username username of etcd auth * @param username username of etcd auth
* @param password password of etcd auth * @param password password of etcd auth
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param load_balancer is the load balance strategy, can be one of
* @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. * round_robin/pick_first/grpclb/xds.
* @param auth_token_ttl TTL seconds for auth token, see also
* `--auth-token-ttl` flags of etcd.
*/ */
Client(std::string const & etcd_url, Client(std::string const& etcd_url, std::string const& username,
std::string const & username, std::string const& password, int const auth_token_ttl = 300,
std::string const & password,
int const auth_token_ttl = 300,
std::string const& load_balancer = "round_robin"); std::string const& load_balancer = "round_robin");
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param username username of etcd auth * @param username username of etcd auth
* @param password password of etcd auth * @param password password of etcd auth
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
* @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. * @param auth_token_ttl TTL seconds for auth token, see also
* Default value should be 300. * `--auth-token-ttl` flags of etcd. Default value should be 300.
*/ */
Client(std::string const & etcd_url, Client(std::string const& etcd_url, std::string const& username,
std::string const & username, std::string const& password, int const auth_token_ttl,
std::string const & password,
int const auth_token_ttl,
#if defined(WITH_GRPC_CHANNEL_CLASS) #if defined(WITH_GRPC_CHANNEL_CLASS)
grpc::ChannelArguments const& arguments grpc::ChannelArguments const& arguments
#else #else
@ -128,12 +128,14 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param username username of etcd auth * @param username username of etcd auth
* @param password password of etcd auth * @param password password of etcd auth
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param load_balancer is the load balance strategy, can be one of
* @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. * round_robin/pick_first/grpclb/xds.
* @param auth_token_ttl TTL seconds for auth token, see also
* `--auth-token-ttl` flags of etcd.
*/ */
static Client* WithUser(std::string const& etcd_url, static Client* WithUser(std::string const& etcd_url,
std::string const& username, std::string const& username,
@ -144,18 +146,17 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param username username of etcd auth * @param username username of etcd auth
* @param password password of etcd auth * @param password password of etcd auth
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
* @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. * @param auth_token_ttl TTL seconds for auth token, see also
* Default value should be 300. * `--auth-token-ttl` flags of etcd. Default value should be 300.
*/ */
static Client* WithUser(std::string const& etcd_url, static Client* WithUser(std::string const& etcd_url,
std::string const& username, std::string const& username,
std::string const & password, std::string const& password, int const auth_token_ttl,
int const auth_token_ttl,
#if defined(WITH_GRPC_CHANNEL_CLASS) #if defined(WITH_GRPC_CHANNEL_CLASS)
grpc::ChannelArguments const& arguments grpc::ChannelArguments const& arguments
#else #else
@ -166,34 +167,35 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param ca root CA file for SSL/TLS connection. * @param ca root CA file for SSL/TLS connection.
* @param cert cert chain file for SSL/TLS authentication, could be empty string. * @param cert cert chain file for SSL/TLS authentication, could be empty
* @param privkey private key file for SSL/TLS authentication, could be empty string. * string.
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param privkey private key file for SSL/TLS authentication, could be empty
* string.
* @param load_balancer is the load balance strategy, can be one of
* round_robin/pick_first/grpclb/xds.
*/ */
Client(std::string const & etcd_url, Client(std::string const& etcd_url, std::string const& ca,
std::string const & ca, std::string const& cert, std::string const& privkey,
std::string const & cert,
std::string const & privkey,
std::string const& target_name_override, std::string const& target_name_override,
std::string const& load_balancer = "round_robin"); std::string const& load_balancer = "round_robin");
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param ca root CA file for SSL/TLS connection. * @param ca root CA file for SSL/TLS connection.
* @param cert cert chain file for SSL/TLS authentication, could be empty string. * @param cert cert chain file for SSL/TLS authentication, could be empty
* @param privkey private key file for SSL/TLS authentication, could be empty string. * string.
* @param privkey private key file for SSL/TLS authentication, could be empty
* string.
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
*/ */
Client(std::string const & etcd_url, Client(std::string const& etcd_url, std::string const& ca,
std::string const & ca, std::string const& cert, std::string const& privkey,
std::string const & cert,
std::string const & privkey,
std::string const& target_name_override, std::string const& target_name_override,
#if defined(WITH_GRPC_CHANNEL_CLASS) #if defined(WITH_GRPC_CHANNEL_CLASS)
grpc::ChannelArguments const& arguments grpc::ChannelArguments const& arguments
@ -205,18 +207,20 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param ca root CA file for SSL/TLS connection. * @param ca root CA file for SSL/TLS connection.
* @param cert cert chain file for SSL/TLS authentication, could be empty string. * @param cert cert chain file for SSL/TLS authentication, could be empty
* @param privkey private key file for SSL/TLS authentication, could be empty string. * string.
* @param target_name_override Override the target host name if you want to pass multiple address * @param privkey private key file for SSL/TLS authentication, could be empty
* for load balancing with SSL, and there's no DNS. The @target_name_override@ must exist in the * string.
* SANS of your SSL certificate. * @param target_name_override Override the target host name if you want to
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * pass multiple address for load balancing with SSL, and there's no DNS. The
* @target_name_override@ must exist in the SANS of your SSL certificate.
* @param load_balancer is the load balance strategy, can be one of
* round_robin/pick_first/grpclb/xds.
*/ */
static Client *WithSSL(std::string const & etcd_url, static Client* WithSSL(std::string const& etcd_url, std::string const& ca,
std::string const & ca,
std::string const& cert = "", std::string const& cert = "",
std::string const& privkey = "", std::string const& privkey = "",
std::string const& target_name_override = "", std::string const& target_name_override = "",
@ -225,14 +229,16 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param ca root CA file for SSL/TLS connection. * @param ca root CA file for SSL/TLS connection.
* @param cert cert chain file for SSL/TLS authentication, could be empty string. * @param cert cert chain file for SSL/TLS authentication, could be empty
* @param privkey private key file for SSL/TLS authentication, could be empty string. * string.
* @param target_name_override Override the target host name if you want to pass multiple address * @param privkey private key file for SSL/TLS authentication, could be empty
* for load balancing with SSL, and there's no DNS. The @target_name_override@ must exist in the * string.
* SANS of your SSL certificate. * @param target_name_override Override the target host name if you want to
* pass multiple address for load balancing with SSL, and there's no DNS. The
* @target_name_override@ must exist in the SANS of your SSL certificate.
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
*/ */
static Client* WithSSL(std::string const& etcd_url, static Client* WithSSL(std::string const& etcd_url,
@ -241,8 +247,7 @@ namespace etcd
#else #else
grpc_impl::ChannelArguments const& arguments, grpc_impl::ChannelArguments const& arguments,
#endif #endif
std::string const & ca, std::string const& ca, std::string const& cert = "",
std::string const & cert = "",
std::string const& privkey = "", std::string const& privkey = "",
std::string const& target_name_override = ""); std::string const& target_name_override = "");
@ -260,29 +265,21 @@ namespace etcd
pplx::task<Response> get(std::string const& key); pplx::task<Response> get(std::string const& key);
/** /**
* Sets the value of a key. The key will be modified if already exists or created * Get the value of specified key of specified revision from the etcd server
* if it does not exists. * @param key is the key to be read
* @param key is the key to be created or modified * @param revision is the revision of the key to be read
* @param value is the new value to be set
*/ */
pplx::task<Response> set(std::string const & key, std::string const & value, int ttl = 0); pplx::task<Response> get(std::string const& key, int64_t revision);
/** /**
* Sets the value of a key. The key will be modified if already exists or created * Sets the value of a key. The key will be modified if already exists or
* if it does not exists. * created if it does not exists.
* @param key is the key to be created or modified * @param key is the key to be created or modified
* @param value is the new value to be set * @param value is the new value to be set
* @param leaseId is the lease attached to the key * @param leaseId is the lease attached to the key
*/ */
pplx::task<Response> set(std::string const & key, std::string const & value, int64_t leaseId); pplx::task<Response> set(std::string const& key, std::string const& value,
const int64_t leaseId = 0);
/**
* Creates a new key and sets it's value. Fails if the key already exists.
* @param key is the key to be created
* @param value is the value to be set
*/
pplx::task<Response> add(std::string const & key, std::string const & value, int ttl = 0);
/** /**
* Creates a new key and sets it's value. Fails if the key already exists. * Creates a new key and sets it's value. Fails if the key already exists.
@ -290,7 +287,8 @@ namespace etcd
* @param value is the value to be set * @param value is the value to be set
* @param leaseId is the lease attached to the key * @param leaseId is the lease attached to the key
*/ */
pplx::task<Response> add(std::string const & key, std::string const & value, int64_t leaseId); pplx::task<Response> add(std::string const& key, std::string const& value,
const int64_t leaseId = 0);
/** /**
* Put a new key-value pair. * Put a new key-value pair.
@ -300,11 +298,13 @@ namespace etcd
pplx::task<Response> put(std::string const& key, std::string const& value); pplx::task<Response> put(std::string const& key, std::string const& value);
/** /**
* Modifies an existing key. Fails if the key does not exists. * Put a new key-value pair.
* @param key is the key to be modified * @param key is the key to be put
* @param value is the new value to be set * @param value is the value to be put
* @param leaseId is the lease id to be associated with the key
*/ */
pplx::task<Response> modify(std::string const & key, std::string const & value, int ttl = 0); pplx::task<Response> put(std::string const& key, std::string const& value,
const int64_t leaseId);
/** /**
* Modifies an existing key. Fails if the key does not exists. * Modifies an existing key. Fails if the key does not exists.
@ -312,81 +312,85 @@ namespace etcd
* @param value is the new value to be set * @param value is the new value to be set
* @param leaseId is the lease attached to the key * @param leaseId is the lease attached to the key
*/ */
pplx::task<Response> modify(std::string const & key, std::string const & value, int64_t leaseId); pplx::task<Response> modify(std::string const& key, std::string const& value,
const int64_t leaseId = 0);
/** /**
* Modifies an existing key only if it has a specific value. Fails if the key does not exists * Modifies an existing key only if it has a specific value. Fails if the key
* or the original value differs from the expected one. * does not exists or the original value differs from the expected one.
* @param key is the key to be modified
* @param value is the new value to be set
* @param old_value is the value to be replaced
*/
pplx::task<Response> modify_if(std::string const & key, std::string const & value, std::string const & old_value, int ttl = 0);
/**
* Modifies an existing key only if it has a specific value. Fails if the key does not exists
* or the original value differs from the expected one.
* @param key is the key to be modified * @param key is the key to be modified
* @param value is the new value to be set * @param value is the new value to be set
* @param old_value is the value to be replaced * @param old_value is the value to be replaced
* @param leaseId is the lease attached to the key * @param leaseId is the lease attached to the key
*/ */
pplx::task<Response> modify_if(std::string const & key, std::string const & value, std::string const & old_value, int64_t leaseId); pplx::task<Response> modify_if(std::string const& key,
std::string const& value,
std::string const& old_value,
const int64_t leaseId = 0);
/** /**
* Modifies an existing key only if it has a specific modification index value. Fails if the key * Modifies an existing key only if it has a specific modification index
* does not exists or the modification index of the previous value differs from the expected one. * value. Fails if the key does not exists or the modification index of the
* @param key is the key to be modified * previous value differs from the expected one.
* @param value is the new value to be set
* @param old_index is the expected index of the original value
*/
pplx::task<Response> modify_if(std::string const & key, std::string const & value, int64_t old_index, int ttl = 0);
/**
* Modifies an existing key only if it has a specific modification index value. Fails if the key
* does not exists or the modification index of the previous value differs from the expected one.
* @param key is the key to be modified * @param key is the key to be modified
* @param value is the new value to be set * @param value is the new value to be set
* @param old_index is the expected index of the original value * @param old_index is the expected index of the original value
* @param leaseId is the lease attached to the key * @param leaseId is the lease attached to the key
*/ */
pplx::task<Response> modify_if(std::string const & key, std::string const & value, int64_t old_index, int64_t leaseId); pplx::task<Response> modify_if(std::string const& key,
std::string const& value, int64_t old_index,
const int64_t leaseId = 0);
/** /**
* Removes a single key. The key has to point to a plain, non directory entry. * Removes a single key. The key has to point to a plain, non directory entry.
* @param key is the key to be deleted * @param key is the key to be deleted
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist.
*/ */
pplx::task<Response> rm(std::string const& key); pplx::task<Response> rm(std::string const& key);
/** /**
* Removes a single key but only if it has a specific value. Fails if the key does not exists * Removes a single key but only if it has a specific value. Fails if the key
* or the its value differs from the expected one. * does not exists or the its value differs from the expected one.
* @param key is the key to be deleted * @param key is the key to be deleted
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist.
*/ */
pplx::task<Response> rm_if(std::string const & key, std::string const & old_value); pplx::task<Response> rm_if(std::string const& key,
std::string const& old_value);
/** /**
* Removes an existing key only if it has a specific modification index value. Fails if the key * Removes an existing key only if it has a specific modification index value.
* does not exists or the modification index of it differs from the expected one. * Fails if the key does not exists or the modification index of it differs
* from the expected one.
* @param key is the key to be deleted * @param key is the key to be deleted
* @param old_index is the expected index of the existing value * @param old_index is the expected index of the existing value
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist.
*/ */
pplx::task<Response> rm_if(std::string const& key, int64_t old_index); pplx::task<Response> rm_if(std::string const& key, int64_t old_index);
/** /**
* Removes a directory node. Fails if the parent directory dos not exists or not a directory. * Removes a directory node. Fails if the parent directory dos not exists or
* not a directory.
* @param key is the directory to be created to be listed * @param key is the directory to be created to be listed
* @param recursive if true then delete a whole subtree, otherwise deletes only an empty directory. * @param recursive if true then delete a whole subtree, otherwise deletes
* only an empty directory.
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted.
*/ */
pplx::task<Response> rmdir(std::string const& key, bool recursive = false); pplx::task<Response> rmdir(std::string const& key, bool recursive = false);
/** /**
* Removes multiple keys between [key, range_end). * Removes multiple keys between [key, range_end).
* *
* This overload for `const char *` is to avoid const char * to bool implicit casting. * This overload for `const char *` is to avoid const char * to bool implicit
* casting.
* *
* @param key is the directory to be created to be listed * @param key is the directory to be created to be listed
* @param range_end is the end of key range to be removed. * @param range_end is the end of key range to be removed.
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted.
*/ */
pplx::task<Response> rmdir(std::string const& key, const char* range_end); pplx::task<Response> rmdir(std::string const& key, const char* range_end);
@ -395,8 +399,11 @@ namespace etcd
* *
* @param key is the directory to be created to be listed * @param key is the directory to be created to be listed
* @param range_end is the end of key range to be removed. * @param range_end is the end of key range to be removed.
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted.
*/ */
pplx::task<Response> rmdir(std::string const & key, std::string const &range_end); pplx::task<Response> rmdir(std::string const& key,
std::string const& range_end);
/** /**
* Gets a directory listing of the directory prefixed by the key. * Gets a directory listing of the directory prefixed by the key.
@ -409,14 +416,28 @@ namespace etcd
* Gets a directory listing of the directory prefixed by the key. * Gets a directory listing of the directory prefixed by the key.
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param limit is the size limit of results to be listed, we don't use default parameters * @param limit is the size limit of results to be listed, we don't use
* to ensure backwards binary compatibility. * default parameters to ensure backwards binary compatibility. 0 means no
* limit.
*/ */
pplx::task<Response> ls(std::string const& key, size_t const limit); pplx::task<Response> ls(std::string const& key, size_t const limit);
/** /**
* Gets a directory listing of the directory identified by the key and range_end, i.e., get * Gets a directory listing of the directory prefixed by the key with given
* all keys in the range [key, range_end). * revision.
*
* @param key is the key to be listed
* @param limit is the size limit of results to be listed, we don't use
* default parameters to ensure backwards binary compatibility. 0 means no
* limit.
* @param revision is the revision to be listed
*/
pplx::task<Response> ls(std::string const& key, size_t const limit,
int64_t revision);
/**
* Gets a directory listing of the directory identified by the key and
* range_end, i.e., get all keys in the range [key, range_end).
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param range_end is the end of key range to be listed * @param range_end is the end of key range to be listed
@ -424,15 +445,31 @@ namespace etcd
pplx::task<Response> ls(std::string const& key, std::string const& range_end); pplx::task<Response> ls(std::string const& key, std::string const& range_end);
/** /**
* Gets a directory listing of the directory identified by the key and range_end, i.e., get * Gets a directory listing of the directory identified by the key and
* all keys in the range [key, range_end), and respects the given limit. * range_end, i.e., get all keys in the range [key, range_end), and respects
* the given limit.
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param range_end is the end of key range to be listed * @param range_end is the end of key range to be listed
* @param limit is the size limit of results to be listed, we don't use default parameters * @param limit is the size limit of results to be listed, we don't use
* to ensure backwards binary compatibility. * default parameters to ensure backwards binary compatibility.
*/ */
pplx::task<Response> ls(std::string const & key, std::string const &range_end, size_t const limit); pplx::task<Response> ls(std::string const& key, std::string const& range_end,
size_t const limit);
/**
* Gets a directory listing of the directory identified by the key and
* range_end, i.e., get all keys in the range [key, range_end), and respects
* the given limit and revision.
*
* @param key is the key to be listed
* @param range_end is the end of key range to be listed
* @param limit is the size limit of results to be listed, we don't use
* default parameters to ensure backwards binary compatibility.
* @param revision is the revision to be listed
*/
pplx::task<Response> ls(std::string const& key, std::string const& range_end,
size_t const limit, int64_t revision);
/** /**
* Gets a directory listing of the directory prefixed by the key. * Gets a directory listing of the directory prefixed by the key.
@ -449,57 +486,93 @@ namespace etcd
* Note that only keys are included in the response. * Note that only keys are included in the response.
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param limit is the size limit of results to be listed, we don't use default parameters * @param limit is the size limit of results to be listed, we don't use
* to ensure backwards binary compatibility. * default parameters to ensure backwards binary compatibility.
*/ */
pplx::task<Response> keys(std::string const& key, size_t const limit); pplx::task<Response> keys(std::string const& key, size_t const limit);
/** /**
* List keys identified by the key and range_end, i.e., get all keys in the range [key, * Gets a directory listing of the directory prefixed by the key.
* range_end), and respects the given limit. *
* Note that only keys are included in the response.
*
* @param key is the key to be listed
* @param limit is the size limit of results to be listed, we don't use
* default parameters to ensure backwards binary compatibility.
* @param revision is the revision to be listed
*/
pplx::task<Response> keys(std::string const& key, size_t const limit,
int64_t revision);
/**
* List keys identified by the key and range_end, i.e., get all keys in the
* range [key, range_end), and respects the given limit.
* *
* Note that only keys are included in the response. * Note that only keys are included in the response.
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param range_end is the end of key range to be listed * @param range_end is the end of key range to be listed
*/ */
pplx::task<Response> keys(std::string const & key, std::string const &range_end); pplx::task<Response> keys(std::string const& key,
std::string const& range_end);
/** /**
* List keys identified by the key and range_end, i.e., get all keys in the range [key, * List keys identified by the key and range_end, i.e., get all keys in the
* range_end). * range [key, range_end).
* *
* Note that only keys are included in the response. * Note that only keys are included in the response.
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param range_end is the end of key range to be listed * @param range_end is the end of key range to be listed
* @param limit is the size limit of results to be listed, we don't use default parameters * @param limit is the size limit of results to be listed, we don't use
* to ensure backwards binary compatibility. * default parameters to ensure backwards binary compatibility.
*/ */
pplx::task<Response> keys(std::string const & key, std::string const &range_end, size_t const limit); pplx::task<Response> keys(std::string const& key,
std::string const& range_end, size_t const limit);
/** /**
* Watches for changes of a key or a subtree. Please note that if you watch e.g. "/testdir" and * List keys identified by the key and range_end, i.e., get all keys in the
* a new key is created, like "/testdir/newkey" then no change happened in the value of * range [key, range_end).
* "/testdir" so your watch will not detect this. If you want to detect addition and deletion of *
* directory entries then you have to do a recursive watch. * Note that only keys are included in the response.
*
* @param key is the key to be listed
* @param range_end is the end of key range to be listed
* @param limit is the size limit of results to be listed, we don't use
* default parameters to ensure backwards binary compatibility.
* @param revision is the revision to be listed
*/
pplx::task<Response> keys(std::string const& key,
std::string const& range_end, size_t const limit,
int64_t revision);
/**
* Watches for changes of a key or a subtree. Please note that if you watch
* e.g. "/testdir" and a new key is created, like "/testdir/newkey" then no
* change happened in the value of
* "/testdir" so your watch will not detect this. If you want to detect
* addition and deletion of directory entries then you have to do a recursive
* watch.
* @param key is the value or directory to be watched * @param key is the value or directory to be watched
* @param recursive if true watch a whole subtree * @param recursive if true watch a whole subtree
*/ */
pplx::task<Response> watch(std::string const& key, bool recursive = false); pplx::task<Response> watch(std::string const& key, bool recursive = false);
/** /**
* Watches for changes of a key or a subtree from a specific index. The index value can be in the "past". * Watches for changes of a key or a subtree from a specific index. The index
* value can be in the "past".
* @param key is the value or directory to be watched * @param key is the value or directory to be watched
* @param fromIndex the first index we are interested in * @param fromIndex the first index we are interested in
* @param recursive if true watch a whole subtree * @param recursive if true watch a whole subtree
*/ */
pplx::task<Response> watch(std::string const & key, int64_t fromIndex, bool recursive = false); pplx::task<Response> watch(std::string const& key, int64_t fromIndex,
bool recursive = false);
/** /**
* Watches for changes of a range of keys inside [key, range_end). * Watches for changes of a range of keys inside [key, range_end).
* *
* This overload for `const char *` is to avoid const char * to bool implicit casting. * This overload for `const char *` is to avoid const char * to bool implicit
* casting.
* *
* @param key is the value or directory to be watched * @param key is the value or directory to be watched
* @param range_end is the end of key range to be removed. * @param range_end is the end of key range to be removed.
@ -512,18 +585,21 @@ namespace etcd
* @param key is the value or directory to be watched * @param key is the value or directory to be watched
* @param range_end is the end of key range to be removed. * @param range_end is the end of key range to be removed.
*/ */
pplx::task<Response> watch(std::string const & key, std::string const &range_end); pplx::task<Response> watch(std::string const& key,
std::string const& range_end);
/** /**
* Watches for changes of a range of keys inside [key, range_end) from a specific index. The index value * Watches for changes of a range of keys inside [key, range_end) from a
* can be in the "past". * specific index. The index value can be in the "past".
* *
* Watches for changes of a key or a subtree from a specific index. The index value can be in the "past". * Watches for changes of a key or a subtree from a specific index. The index
* value can be in the "past".
* @param key is the value or directory to be watched * @param key is the value or directory to be watched
* @param range_end is the end of key range to be removed. * @param range_end is the end of key range to be removed.
* @param fromIndex the first index we are interested in * @param fromIndex the first index we are interested in
*/ */
pplx::task<Response> watch(std::string const & key, std::string const &range_end, int64_t fromIndex); pplx::task<Response> watch(std::string const& key,
std::string const& range_end, int64_t fromIndex);
/** /**
* Grants a lease. * Grants a lease.
@ -555,30 +631,52 @@ namespace etcd
pplx::task<Response> leases(); pplx::task<Response> leases();
/** /**
* Gains a lock at a key, using a default created lease, using the default lease (10 seconds), with * Add an etcd member to the etcd cluster, equivalent to `etcdctl member add`.
* keeping alive has already been taken care of by the library. * @param peer_urls is comma separated list of URLs for the new member.
* @param is_learner is true if the added member is a learner.
*/
pplx::task<Response> add_member(std::string const& peer_urls,
bool is_learner);
/**
* List all members, equivalent to `etcdctl member list`.
*/
pplx::task<Response> list_member();
/**
* Add an etcd member to the etcd cluster, equivalent to `etcdctl member add`.
* @param member_id is the ID of the member to be removed.
*/
pplx::task<Response> remove_member(const uint64_t member_id);
/**
* Gains a lock at a key, using a default created lease, using the default
* lease (10 seconds), with keeping alive has already been taken care of by
* the library.
* @param key is the key to be used to request the lock. * @param key is the key to be used to request the lock.
*/ */
pplx::task<Response> lock(std::string const& key); pplx::task<Response> lock(std::string const& key);
/** /**
* Lock is special, as waiting for lock may cause the thread resources (in the pplx thread pool) to * Lock is special, as waiting for lock may cause the thread resources (in the
* be exhausted. So we need to provide a way to let the user to issue a lock without taking the a * pplx thread pool) to be exhausted. So we need to provide a way to let the
* shared thread. * user to issue a lock without taking the a shared thread.
* *
* This works like what we have in the sync client, but we can issue such method from the async client * This works like what we have in the sync client, but we can issue such
* directly. * method from the async client directly.
* *
* That would be useful when use already issue the lock from a controlled thread. See more discussion * That would be useful when use already issue the lock from a controlled
* about the target scenario in https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/issues/139 * thread. See more discussion about the target scenario in
* https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3/issues/139
* *
* @param key is the key to be used to request the lock. * @param key is the key to be used to request the lock.
*/ */
pplx::task<Response> lock(std::string const& key, const bool sync); pplx::task<Response> lock(std::string const& key, const bool sync);
/** /**
* Gains a lock at a key, using a default created lease, using the specified lease TTL (in seconds), with * Gains a lock at a key, using a default created lease, using the specified
* keeping alive has already been taken care of by the library. * lease TTL (in seconds), with keeping alive has already been taken care of
* by the library.
* @param key is the key to be used to request the lock. * @param key is the key to be used to request the lock.
* @param lease_ttl is the TTL used to create a lease for the key. * @param lease_ttl is the TTL used to create a lease for the key.
*/ */
@ -587,19 +685,22 @@ namespace etcd
/** /**
* Lock, but the sync version. * Lock, but the sync version.
*/ */
pplx::task<Response> lock(std::string const &key, int lease_ttl, const bool sync); pplx::task<Response> lock(std::string const& key, int lease_ttl,
const bool sync);
/** /**
* Gains a lock at a key, using a user-provided lease, the lifetime of the lease won't be taken care * Gains a lock at a key, using a user-provided lease, the lifetime of the
* of by the library. * lease won't be taken care of by the library.
* @param key is the key to be used to request the lock. * @param key is the key to be used to request the lock.
*/ */
pplx::task<Response> lock_with_lease(std::string const &key, int64_t lease_id); pplx::task<Response> lock_with_lease(std::string const& key,
int64_t lease_id);
/** /**
* Lock, but the sync version. * Lock, but the sync version.
*/ */
pplx::task<Response> lock_with_lease(std::string const &key, int64_t lease_id, const bool sync); pplx::task<Response> lock_with_lease(std::string const& key, int64_t lease_id,
const bool sync);
/** /**
* Releases a lock at a key. * Releases a lock at a key.
@ -641,7 +742,8 @@ namespace etcd
* @param value is the new value to set. * @param value is the new value to set.
*/ */
pplx::task<Response> proclaim(std::string const& name, int64_t lease_id, pplx::task<Response> proclaim(std::string const& name, int64_t lease_id,
std::string const &key, int64_t revision, std::string const &value); std::string const& key, int64_t revision,
std::string const& value);
/** /**
* Get the current leader proclamation. * Get the current leader proclamation.
@ -659,7 +761,8 @@ namespace etcd
* *
* @param name is the names of election to watch. * @param name is the names of election to watch.
* *
* @returns an observer that holds that action and will cancel the request when being destructed. * @returns an observer that holds that action and will cancel the request
* when being destructed.
*/ */
std::unique_ptr<Observer> observe(std::string const& name); std::unique_ptr<Observer> observe(std::string const& name);
@ -693,7 +796,8 @@ namespace etcd
* Set a timeout value for grpc operations. * Set a timeout value for grpc operations.
*/ */
template <typename Period = std::micro> template <typename Period = std::micro>
void set_grpc_timeout(std::chrono::duration<std::chrono::microseconds::rep, Period> const &timeout) { void set_grpc_timeout(std::chrono::duration<std::chrono::microseconds::rep,
Period> const& timeout) {
this->client->set_grpc_timeout(timeout); this->client->set_grpc_timeout(timeout);
} }
@ -714,6 +818,6 @@ namespace etcd
SyncClient* client = nullptr; SyncClient* client = nullptr;
}; };
} } // namespace etcd
#endif #endif

View File

@ -3,66 +3,50 @@
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <condition_variable>
#include <exception> #include <exception>
#include <functional> #include <functional>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <thread> #include <thread>
#include "etcd/SyncClient.hpp"
#include "etcd/Response.hpp" #include "etcd/Response.hpp"
#include "etcd/SyncClient.hpp"
#include <boost/config.hpp> namespace etcd {
#if BOOST_VERSION >= 106600
#include <boost/asio/io_context.hpp>
#else
#include <boost/asio/io_service.hpp>
#endif
#include <boost/asio/steady_timer.hpp>
namespace etcd
{
// forward declaration to avoid header/library dependency // forward declaration to avoid header/library dependency
class Client; class Client;
/** /**
* If ID is set to 0, the library will choose an ID, and can be accessed from ".Lease()". * If ID is set to 0, the library will choose an ID, and can be accessed from
* ".Lease()".
*/ */
class KeepAlive class KeepAlive {
{
public: public:
KeepAlive(Client const &client, KeepAlive(Client const& client, int ttl, int64_t lease_id = 0);
int ttl, int64_t lease_id = 0); KeepAlive(SyncClient const& client, int ttl, int64_t lease_id = 0);
KeepAlive(SyncClient const &client, KeepAlive(std::string const& address, int ttl, int64_t lease_id = 0);
int ttl, int64_t lease_id = 0); KeepAlive(std::string const& address, std::string const& username,
KeepAlive(std::string const & address, std::string const& password, int ttl, int64_t lease_id = 0,
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); int const auth_token_ttl = 300);
KeepAlive(Client const& client, KeepAlive(Client const& client,
std::function<void (std::exception_ptr)> const &handler, std::function<void(std::exception_ptr)> const& handler, int ttl,
int ttl, int64_t lease_id = 0); int64_t lease_id = 0);
KeepAlive(SyncClient const& client, KeepAlive(SyncClient const& client,
std::function<void (std::exception_ptr)> const &handler, std::function<void(std::exception_ptr)> const& handler, int ttl,
int ttl, int64_t lease_id = 0); int64_t lease_id = 0);
KeepAlive(std::string const& address, KeepAlive(std::string const& address,
std::function<void (std::exception_ptr)> const &handler, std::function<void(std::exception_ptr)> const& handler, int ttl,
int ttl, int64_t lease_id = 0); int64_t lease_id = 0);
KeepAlive(std::string const & address, KeepAlive(std::string const& address, std::string const& username,
std::string const & username, std::string const & password, std::string const& password,
std::function<void (std::exception_ptr)> const &handler, std::function<void(std::exception_ptr)> const& handler, int ttl,
int ttl, int64_t lease_id = 0, int64_t lease_id = 0, int const auth_token_ttl = 300);
int const auth_token_ttl = 300); KeepAlive(std::string const& address, std::string const& ca,
KeepAlive(std::string const & address, std::string const& cert, std::string const& privkey,
std::string const & ca, std::function<void(std::exception_ptr)> const& handler, int ttl,
std::string const & cert, int64_t lease_id = 0, std::string const& target_name_override = "");
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 const&) = delete;
KeepAlive(KeepAlive&&) = delete; KeepAlive(KeepAlive&&) = delete;
@ -75,7 +59,8 @@ namespace etcd
void Cancel(); void Cancel();
/** /**
* Check if the keep alive is still valid (invalid when there's an async exception). * Check if the keep alive is still valid (invalid when there's an async
* exception).
* *
* Nothing will happen if valid and an exception will be rethrowed if invalid. * Nothing will happen if valid and an exception will be rethrowed if invalid.
*/ */
@ -86,7 +71,8 @@ namespace etcd
*/ */
template <typename Rep = std::micro> template <typename Rep = std::micro>
void set_grpc_timeout(std::chrono::duration<Rep> const& timeout) { void set_grpc_timeout(std::chrono::duration<Rep> const& timeout) {
this->grpc_timeout = std::chrono::duration_cast<std::chrono::milliseconds>(timeout); this->grpc_timeout =
std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
} }
/** /**
@ -99,7 +85,10 @@ namespace etcd
~KeepAlive(); ~KeepAlive();
protected: protected:
void refresh(); // automatically refresh loop, returns the error message if failed
std::string refresh();
// refresh once immediately, returns the error message if failed
std::string refresh_once();
struct EtcdServerStubs; struct EtcdServerStubs;
struct EtcdServerStubsDeleter { struct EtcdServerStubsDeleter {
@ -112,27 +101,23 @@ namespace etcd
std::exception_ptr eptr_; std::exception_ptr eptr_;
std::function<void(std::exception_ptr)> handler_; std::function<void(std::exception_ptr)> handler_;
// Don't use `pplx::task` to avoid sharing thread pool with other actions on the client // Don't use `pplx::task` to avoid sharing thread pool with other actions on
// to avoid any potential blocking, which may block the keepalive loop and evict the lease. // the client to avoid any potential blocking, which may block the keepalive
std::thread task_; // loop and evict the lease.
std::thread refresh_task_;
int ttl; int ttl;
int64_t lease_id; int64_t lease_id;
// protect the initializing status of `timer`. // protect the initializing status of `timer`.
std::mutex mutex_for_refresh_; std::mutex mutex_for_refresh_;
std::condition_variable cv_for_refresh_;
std::atomic_bool continue_next; std::atomic_bool continue_next;
// grpc timeout in `refresh()` // grpc timeout in `refresh()`
mutable std::chrono::microseconds grpc_timeout = std::chrono::microseconds::zero(); mutable std::chrono::microseconds grpc_timeout =
std::chrono::microseconds::zero();
#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_;
}; };
} } // namespace etcd
#endif #endif

View File

@ -2,23 +2,23 @@
#define __ETCD_RESPONSE_HPP__ #define __ETCD_RESPONSE_HPP__
#include <chrono> #include <chrono>
#include <iostream>
#include <functional> #include <functional>
#include <iostream>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "etcd/Value.hpp" #include "etcd/Value.hpp"
#include "etcd/v3/Member.hpp"
namespace etcdv3 { namespace etcdv3 {
class AsyncWatchAction; class AsyncWatchAction;
class AsyncLeaseKeepAliveAction; class AsyncLeaseKeepAliveAction;
class AsyncObserveAction; class AsyncObserveAction;
class V3Response; class V3Response;
} } // namespace etcdv3
namespace etcd namespace etcd {
{
typedef std::vector<std::string> Keys; typedef std::vector<std::string> Keys;
namespace detail { namespace detail {
@ -28,62 +28,60 @@ namespace etcd
return std::chrono::duration_cast<std::chrono::microseconds>( return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now() - start_timepoint); std::chrono::high_resolution_clock::now() - start_timepoint);
} }
} } // namespace detail
// forward declaration // forward declaration
class KeepAlive; class KeepAlive;
class Watcher; class Watcher;
/** /**
* The Reponse object received for the requests of etcd::Client * The Response object received for the requests of etcd::Client
*/ */
class Response class Response {
{
public: public:
template <typename T> template <typename T>
static etcd::Response create(std::unique_ptr<T> call) static etcd::Response create(std::unique_ptr<T> call) {
{
call->waitForResponse(); call->waitForResponse();
auto v3resp = call->ParseResponse(); auto v3resp = call->ParseResponse();
return etcd::Response(v3resp, detail::duration_till_now(call->startTimepoint())); return etcd::Response(v3resp,
detail::duration_till_now(call->startTimepoint()));
} }
template <typename T> template <typename T>
static etcd::Response create(std::shared_ptr<T> call) static etcd::Response create(std::shared_ptr<T> call) {
{
call->waitForResponse(); call->waitForResponse();
auto v3resp = call->ParseResponse(); auto v3resp = call->ParseResponse();
return etcd::Response(v3resp, detail::duration_till_now(call->startTimepoint())); return etcd::Response(v3resp,
detail::duration_till_now(call->startTimepoint()));
} }
template <typename T> template <typename T>
static etcd::Response create(std::unique_ptr<T> call, static etcd::Response create(std::unique_ptr<T> call,
std::function<void(Response)> callback) std::function<void(Response)> callback) {
{
call->waitForResponse(callback); call->waitForResponse(callback);
auto v3resp = call->ParseResponse(); auto v3resp = call->ParseResponse();
return etcd::Response(v3resp, detail::duration_till_now(call->startTimepoint())); return etcd::Response(v3resp,
detail::duration_till_now(call->startTimepoint()));
} }
template <typename T> template <typename T>
static etcd::Response create(std::function<std::unique_ptr<T>()> fn) static etcd::Response create(std::function<std::unique_ptr<T>()> fn) {
{
auto call = fn(); auto call = fn();
call->waitForResponse(); call->waitForResponse();
auto v3resp = call->ParseResponse(); auto v3resp = call->ParseResponse();
return etcd::Response(v3resp, detail::duration_till_now(call->startTimepoint())); return etcd::Response(v3resp,
detail::duration_till_now(call->startTimepoint()));
} }
template <typename T> template <typename T>
static etcd::Response create(std::function<std::shared_ptr<T>()> fn) static etcd::Response create(std::function<std::shared_ptr<T>()> fn) {
{
auto call = fn(); auto call = fn();
call->waitForResponse(); call->waitForResponse();
auto v3resp = call->ParseResponse(); auto v3resp = call->ParseResponse();
return etcd::Response(v3resp, detail::duration_till_now(call->startTimepoint())); return etcd::Response(v3resp,
detail::duration_till_now(call->startTimepoint()));
} }
Response(); Response();
@ -91,7 +89,8 @@ namespace etcd
Response(const Response&); Response(const Response&);
/** /**
* Returns the error code received from the etcd server. In case of success the error code is 0. * Returns the error code received from the etcd server. In case of success
* the error code is 0.
*/ */
int error_code() const; int error_code() const;
@ -131,17 +130,20 @@ namespace etcd
Value const& value() const; Value const& value() const;
/** /**
* Returns the previous value object of the response to a set/modify/rm operation. * Returns the previous value object of the response to a set/modify/rm
* operation.
*/ */
Value const& prev_value() const; Value const& prev_value() const;
/** /**
* Returns the index-th value of the response to an 'ls' operation. Equivalent to values()[index] * Returns the index-th value of the response to an 'ls' operation. Equivalent
* to values()[index]
*/ */
Value const& value(int index) const; Value const& value(int index) const;
/** /**
* Returns the vector of values in a directory in response to an 'ls' operation. * Returns the vector of values in a directory in response to an 'ls'
* operation.
*/ */
Values const& values() const; Values const& values() const;
@ -161,6 +163,12 @@ namespace etcd
*/ */
int64_t compact_revision() const; int64_t compact_revision() const;
/**
* Returns the watcher id for client.watch() requests. `-1` means
* uninitialized (the response is not for watch).
*/
int64_t watch_id() const;
/** /**
* Returns the lock key. * Returns the lock key.
*/ */
@ -201,8 +209,14 @@ namespace etcd
*/ */
std::vector<int64_t> const& leases() const; std::vector<int64_t> const& leases() const;
/**
* Returns the member list.
*/
std::vector<etcdv3::Member> const& members() const;
protected: protected:
Response(const etcdv3::V3Response& response, std::chrono::microseconds const& duration); Response(const etcdv3::V3Response& response,
std::chrono::microseconds const& duration);
Response(int error_code, std::string const& error_message); Response(int error_code, std::string const& error_message);
Response(int error_code, char const* error_message); Response(int error_code, char const* error_message);
@ -215,10 +229,12 @@ namespace etcd
Values _values; Values _values;
Keys _keys; Keys _keys;
int64_t _compact_revision = -1; // for watch int64_t _compact_revision = -1; // for watch
int64_t _watch_id = -1; // for watch
std::string _lock_key; // for lock std::string _lock_key; // for lock
std::string _name; // for campaign (in v3election) std::string _name; // for campaign (in v3election)
std::vector<Event> _events; // for watch std::vector<Event> _events; // for watch
// execute duration (in microseconds), during the action created and response parsed // execute duration (in microseconds), during the action created and response
// parsed
std::chrono::microseconds _duration; std::chrono::microseconds _duration;
// cluster metadata // cluster metadata
@ -229,6 +245,9 @@ namespace etcd
// for lease list // for lease list
std::vector<int64_t> _leases; std::vector<int64_t> _leases;
// for member list
std::vector<etcdv3::Member> _members;
friend class Client; friend class Client;
friend class SyncClient; friend class SyncClient;
friend class KeepAlive; friend class KeepAlive;
@ -238,6 +257,6 @@ namespace etcd
friend class etcdv3::AsyncLeaseKeepAliveAction; friend class etcdv3::AsyncLeaseKeepAliveAction;
friend class etcdv3::AsyncObserveAction; friend class etcdv3::AsyncObserveAction;
}; };
} } // namespace etcd
#endif #endif

View File

@ -29,6 +29,9 @@ namespace etcdv3 {
class AsyncLeaseTimeToLiveAction; class AsyncLeaseTimeToLiveAction;
class AsyncLeaseLeasesAction; class AsyncLeaseLeasesAction;
class AsyncLockAction; class AsyncLockAction;
class AsyncAddMemberAction;
class AsyncListMemberAction;
class AsyncRemoveMemberAction;
class AsyncUnlockAction; class AsyncUnlockAction;
class AsyncPutAction; class AsyncPutAction;
class AsyncRangeAction; class AsyncRangeAction;
@ -42,27 +45,27 @@ namespace etcdv3 {
namespace detail { namespace detail {
std::string string_plus_one(std::string const& value); std::string string_plus_one(std::string const& value);
} std::string resolve_etcd_endpoints(std::string const& default_endpoints);
} } // namespace detail
} // namespace etcdv3
#if defined(WITH_GRPC_CHANNEL_CLASS) #if defined(WITH_GRPC_CHANNEL_CLASS)
namespace grpc { namespace grpc {
class Channel; class Channel;
class ChannelArguments; class ChannelArguments;
} } // namespace grpc
#else #else
namespace grpc_impl { namespace grpc_impl {
class Channel; class Channel;
class ChannelArguments; class ChannelArguments;
} } // namespace grpc_impl
#endif #endif
namespace etcd namespace etcd {
{ using etcdv3::ERROR_ACTION_CANCELLED;
using etcdv3::ERROR_KEY_NOT_FOUND;
using etcdv3::ERROR_COMPARE_FAILED; using etcdv3::ERROR_COMPARE_FAILED;
using etcdv3::ERROR_KEY_ALREADY_EXISTS; using etcdv3::ERROR_KEY_ALREADY_EXISTS;
using etcdv3::ERROR_ACTION_CANCELLED; using etcdv3::ERROR_KEY_NOT_FOUND;
class KeepAlive; class KeepAlive;
class Watcher; class Watcher;
@ -73,15 +76,16 @@ namespace etcd
* Etcd operations can be reached via the methods of the client. * Etcd operations can be reached via the methods of the client.
*/ */
/** /**
* SyncClient is a wrapper around etcd::Client and provides a simplified sync interface with blocking operations. * SyncClient is a wrapper around etcd::Client and provides a simplified sync
* interface with blocking operations.
* *
* In case of any exceptions occur in the backend the Response value's error_message will contain the * In case of any exceptions occur in the backend the Response value's
* text of the exception and the error code will be 600 * error_message will contain the text of the exception and the error code will
* be 600
* *
* Use this class only if performance does not matter. * Use this class only if performance does not matter.
*/ */
class SyncClient class SyncClient {
{
private: private:
class TokenAuthenticator; class TokenAuthenticator;
class TokenAuthenticatorDeleter { class TokenAuthenticatorDeleter {
@ -93,9 +97,10 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param load_balancer is the load balance strategy, can be one of
* round_robin/pick_first/grpclb/xds.
*/ */
SyncClient(std::string const& etcd_url, SyncClient(std::string const& etcd_url,
std::string const& load_balancer = "round_robin"); std::string const& load_balancer = "round_robin");
@ -103,8 +108,8 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
*/ */
SyncClient(std::string const& etcd_url, SyncClient(std::string const& etcd_url,
@ -118,9 +123,10 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param load_balancer is the load balance strategy, can be one of
* round_robin/pick_first/grpclb/xds.
*/ */
static SyncClient* WithUrl(std::string const& etcd_url, static SyncClient* WithUrl(std::string const& etcd_url,
std::string const& load_balancer = "round_robin"); std::string const& load_balancer = "round_robin");
@ -128,8 +134,8 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
*/ */
static SyncClient* WithUrl(std::string const& etcd_url, static SyncClient* WithUrl(std::string const& etcd_url,
@ -143,34 +149,32 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param username username of etcd auth * @param username username of etcd auth
* @param password password of etcd auth * @param password password of etcd auth
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param load_balancer is the load balance strategy, can be one of
* @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. * round_robin/pick_first/grpclb/xds.
* @param auth_token_ttl TTL seconds for auth token, see also
* `--auth-token-ttl` flags of etcd.
*/ */
SyncClient(std::string const & etcd_url, SyncClient(std::string const& etcd_url, std::string const& username,
std::string const & username, std::string const& password, int const auth_token_ttl = 300,
std::string const & password,
int const auth_token_ttl = 300,
std::string const& load_balancer = "round_robin"); std::string const& load_balancer = "round_robin");
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param username username of etcd auth * @param username username of etcd auth
* @param password password of etcd auth * @param password password of etcd auth
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
* @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. * @param auth_token_ttl TTL seconds for auth token, see also
* Default value should be 300. * `--auth-token-ttl` flags of etcd. Default value should be 300.
*/ */
SyncClient(std::string const & etcd_url, SyncClient(std::string const& etcd_url, std::string const& username,
std::string const & username, std::string const& password, int const auth_token_ttl,
std::string const & password,
int const auth_token_ttl,
#if defined(WITH_GRPC_CHANNEL_CLASS) #if defined(WITH_GRPC_CHANNEL_CLASS)
grpc::ChannelArguments const& arguments grpc::ChannelArguments const& arguments
#else #else
@ -178,16 +182,17 @@ namespace etcd
#endif #endif
); );
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param username username of etcd auth * @param username username of etcd auth
* @param password password of etcd auth * @param password password of etcd auth
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param load_balancer is the load balance strategy, can be one of
* @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. * round_robin/pick_first/grpclb/xds.
* @param auth_token_ttl TTL seconds for auth token, see also
* `--auth-token-ttl` flags of etcd.
*/ */
static SyncClient* WithUser(std::string const& etcd_url, static SyncClient* WithUser(std::string const& etcd_url,
std::string const& username, std::string const& username,
@ -198,13 +203,13 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param username username of etcd auth * @param username username of etcd auth
* @param password password of etcd auth * @param password password of etcd auth
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
* @param auth_token_ttl TTL seconds for auth token, see also `--auth-token-ttl` flags of etcd. * @param auth_token_ttl TTL seconds for auth token, see also
* Default value should be 300. * `--auth-token-ttl` flags of etcd. Default value should be 300.
*/ */
static SyncClient* WithUser(std::string const& etcd_url, static SyncClient* WithUser(std::string const& etcd_url,
std::string const& username, std::string const& username,
@ -217,38 +222,38 @@ namespace etcd
#endif #endif
); );
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param ca root CA file for SSL/TLS connection. * @param ca root CA file for SSL/TLS connection.
* @param cert cert chain file for SSL/TLS authentication, could be empty string. * @param cert cert chain file for SSL/TLS authentication, could be empty
* @param privkey private key file for SSL/TLS authentication, could be empty string. * string.
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * @param privkey private key file for SSL/TLS authentication, could be empty
* string.
* @param load_balancer is the load balance strategy, can be one of
* round_robin/pick_first/grpclb/xds.
*/ */
SyncClient(std::string const & etcd_url, SyncClient(std::string const& etcd_url, std::string const& ca,
std::string const & ca, std::string const& cert, std::string const& privkey,
std::string const & cert,
std::string const & privkey,
std::string const& target_name_override, std::string const& target_name_override,
std::string const& load_balancer = "round_robin"); std::string const& load_balancer = "round_robin");
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param ca root CA file for SSL/TLS connection. * @param ca root CA file for SSL/TLS connection.
* @param cert cert chain file for SSL/TLS authentication, could be empty string. * @param cert cert chain file for SSL/TLS authentication, could be empty
* @param privkey private key file for SSL/TLS authentication, could be empty string. * string.
* @param privkey private key file for SSL/TLS authentication, could be empty
* string.
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
*/ */
SyncClient(std::string const & etcd_url, SyncClient(std::string const& etcd_url, std::string const& ca,
std::string const & ca, std::string const& cert, std::string const& privkey,
std::string const & cert,
std::string const & privkey,
std::string const& target_name_override, std::string const& target_name_override,
#if defined(WITH_GRPC_CHANNEL_CLASS) #if defined(WITH_GRPC_CHANNEL_CLASS)
grpc::ChannelArguments const& arguments grpc::ChannelArguments const& arguments
@ -257,22 +262,23 @@ namespace etcd
#endif #endif
); );
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param ca root CA file for SSL/TLS connection. * @param ca root CA file for SSL/TLS connection.
* @param cert cert chain file for SSL/TLS authentication, could be empty string. * @param cert cert chain file for SSL/TLS authentication, could be empty
* @param privkey private key file for SSL/TLS authentication, could be empty string. * string.
* @param target_name_override Override the target host name if you want to pass multiple address * @param privkey private key file for SSL/TLS authentication, could be empty
* for load balancing with SSL, and there's no DNS. The @target_name_override@ must exist in the * string.
* SANS of your SSL certificate. * @param target_name_override Override the target host name if you want to
* @param load_balancer is the load balance strategy, can be one of round_robin/pick_first/grpclb/xds. * pass multiple address for load balancing with SSL, and there's no DNS. The
* @target_name_override@ must exist in the SANS of your SSL certificate.
* @param load_balancer is the load balance strategy, can be one of
* round_robin/pick_first/grpclb/xds.
*/ */
static SyncClient *WithSSL(std::string const & etcd_url, static SyncClient* WithSSL(std::string const& etcd_url, std::string const& ca,
std::string const & ca,
std::string const& cert = "", std::string const& cert = "",
std::string const& privkey = "", std::string const& privkey = "",
std::string const& target_name_override = "", std::string const& target_name_override = "",
@ -281,14 +287,16 @@ namespace etcd
/** /**
* Constructs an etcd client object. * Constructs an etcd client object.
* *
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:2379", * @param etcd_url is the url of the etcd server to connect to, like
* or multiple url, separated by ',' or ';'. * "http://127.0.0.1:2379", or multiple url, separated by ',' or ';'.
* @param ca root CA file for SSL/TLS connection. * @param ca root CA file for SSL/TLS connection.
* @param cert cert chain file for SSL/TLS authentication, could be empty string. * @param cert cert chain file for SSL/TLS authentication, could be empty
* @param privkey private key file for SSL/TLS authentication, could be empty string. * string.
* @param target_name_override Override the target host name if you want to pass multiple address * @param privkey private key file for SSL/TLS authentication, could be empty
* for load balancing with SSL, and there's no DNS. The @target_name_override@ must exist in the * string.
* SANS of your SSL certificate. * @param target_name_override Override the target host name if you want to
* pass multiple address for load balancing with SSL, and there's no DNS. The
* @target_name_override@ must exist in the SANS of your SSL certificate.
* @param arguments user provided grpc channel arguments. * @param arguments user provided grpc channel arguments.
*/ */
static SyncClient* WithSSL(std::string const& etcd_url, static SyncClient* WithSSL(std::string const& etcd_url,
@ -316,28 +324,21 @@ namespace etcd
Response get(std::string const& key); Response get(std::string const& key);
/** /**
* Sets the value of a key. The key will be modified if already exists or created * Get the value of specified key of specified revision from the etcd server
* if it does not exists. * @param key is the key to be read
* @param key is the key to be created or modified * @param revision is the revision of the key to be read
* @param value is the new value to be set
*/ */
Response set(std::string const & key, std::string const & value, int ttl = 0); Response get(std::string const& key, int64_t revision);
/** /**
* Sets the value of a key. The key will be modified if already exists or created * Sets the value of a key. The key will be modified if already exists or
* if it does not exists. * created if it does not exists.
* @param key is the key to be created or modified * @param key is the key to be created or modified
* @param value is the new value to be set * @param value is the new value to be set
* @param leaseId is the lease attached to the key * @param leaseId is the lease attached to the key
*/ */
Response set(std::string const & key, std::string const & value, int64_t leaseId); Response set(std::string const& key, std::string const& value,
const int64_t leaseId = 0);
/**
* Creates a new key and sets it's value. Fails if the key already exists.
* @param key is the key to be created
* @param value is the value to be set
*/
Response add(std::string const & key, std::string const & value, int ttl = 0);
/** /**
* Creates a new key and sets it's value. Fails if the key already exists. * Creates a new key and sets it's value. Fails if the key already exists.
@ -345,7 +346,8 @@ namespace etcd
* @param value is the value to be set * @param value is the value to be set
* @param leaseId is the lease attached to the key * @param leaseId is the lease attached to the key
*/ */
Response add(std::string const & key, std::string const & value, int64_t leaseId); Response add(std::string const& key, std::string const& value,
const int64_t leaseId = 0);
/** /**
* Put a new key-value pair. * Put a new key-value pair.
@ -355,11 +357,13 @@ namespace etcd
Response put(std::string const& key, std::string const& value); Response put(std::string const& key, std::string const& value);
/** /**
* Modifies an existing key. Fails if the key does not exists. * Put a new key-value pair.
* @param key is the key to be modified * @param key is the key to be put
* @param value is the new value to be set * @param value is the value to be put
* @param leaseId is the lease id to be associated with the key
*/ */
Response modify(std::string const & key, std::string const & value, int ttl = 0); Response put(std::string const& key, std::string const& value,
const int64_t leaseId);
/** /**
* Modifies an existing key. Fails if the key does not exists. * Modifies an existing key. Fails if the key does not exists.
@ -367,81 +371,81 @@ namespace etcd
* @param value is the new value to be set * @param value is the new value to be set
* @param leaseId is the lease attached to the key * @param leaseId is the lease attached to the key
*/ */
Response modify(std::string const & key, std::string const & value, int64_t leaseId); Response modify(std::string const& key, std::string const& value,
const int64_t leaseId = 0);
/** /**
* Modifies an existing key only if it has a specific value. Fails if the key does not exists * Modifies an existing key only if it has a specific value. Fails if the key
* or the original value differs from the expected one. * does not exists or the original value differs from the expected one.
* @param key is the key to be modified
* @param value is the new value to be set
* @param old_value is the value to be replaced
*/
Response modify_if(std::string const & key, std::string const & value, std::string const & old_value, int ttl = 0);
/**
* Modifies an existing key only if it has a specific value. Fails if the key does not exists
* or the original value differs from the expected one.
* @param key is the key to be modified * @param key is the key to be modified
* @param value is the new value to be set * @param value is the new value to be set
* @param old_value is the value to be replaced * @param old_value is the value to be replaced
* @param leaseId is the lease attached to the key * @param leaseId is the lease attached to the key
*/ */
Response modify_if(std::string const & key, std::string const & value, std::string const & old_value, int64_t leaseId); Response modify_if(std::string const& key, std::string const& value,
std::string const& old_value, const int64_t leaseId = 0);
/** /**
* Modifies an existing key only if it has a specific modification index value. Fails if the key * Modifies an existing key only if it has a specific modification index
* does not exists or the modification index of the previous value differs from the expected one. * value. Fails if the key does not exists or the modification index of the
* @param key is the key to be modified * previous value differs from the expected one.
* @param value is the new value to be set
* @param old_index is the expected index of the original value
*/
Response modify_if(std::string const & key, std::string const & value, int64_t old_index, int ttl = 0);
/**
* Modifies an existing key only if it has a specific modification index value. Fails if the key
* does not exists or the modification index of the previous value differs from the expected one.
* @param key is the key to be modified * @param key is the key to be modified
* @param value is the new value to be set * @param value is the new value to be set
* @param old_index is the expected index of the original value * @param old_index is the expected index of the original value
* @param leaseId is the lease attached to the key * @param leaseId is the lease attached to the key
*/ */
Response modify_if(std::string const & key, std::string const & value, int64_t old_index, int64_t leaseId); Response modify_if(std::string const& key, std::string const& value,
int64_t old_index, const int64_t leaseId = 0);
/** /**
* Removes a single key. The key has to point to a plain, non directory entry. * Removes a single key. The key has to point to a plain, non directory entry.
* @param key is the key to be deleted * @param key is the key to be deleted
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist.
*/ */
Response rm(std::string const& key); Response rm(std::string const& key);
/** /**
* Removes a single key but only if it has a specific value. Fails if the key does not exists * Removes a single key but only if it has a specific value. Fails if the key
* or the its value differs from the expected one. * does not exists or the its value differs from the expected one.
* @param key is the key to be deleted * @param key is the key to be deleted
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist.
*/ */
Response rm_if(std::string const& key, std::string const& old_value); Response rm_if(std::string const& key, std::string const& old_value);
/** /**
* Removes an existing key only if it has a specific modification index value. Fails if the key * Removes an existing key only if it has a specific modification index value.
* does not exists or the modification index of it differs from the expected one. * Fails if the key does not exists or the modification index of it differs
* from the expected one.
* @param key is the key to be deleted * @param key is the key to be deleted
* @param old_index is the expected index of the existing value * @param old_index is the expected index of the existing value
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the key does not exist.
*/ */
Response rm_if(std::string const& key, int64_t old_index); Response rm_if(std::string const& key, int64_t old_index);
/** /**
* Removes a directory node. Fails if the parent directory dos not exists or not a directory. * Removes a directory node. Fails if the parent directory dos not exists or
* not a directory.
* @param key is the directory to be created to be listed * @param key is the directory to be created to be listed
* @param recursive if true then delete a whole subtree, otherwise deletes only an empty directory. * @param recursive if true then delete a whole subtree, otherwise deletes
* only an empty directory.
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted.
*/ */
Response rmdir(std::string const& key, bool recursive = false); Response rmdir(std::string const& key, bool recursive = false);
/** /**
* Removes multiple keys between [key, range_end). * Removes multiple keys between [key, range_end).
* *
* This overload for `const char *` is to avoid const char * to bool implicit casting. * This overload for `const char *` is to avoid const char * to bool implicit
* casting.
* *
* @param key is the directory to be created to be listed * @param key is the directory to be created to be listed
* @param range_end is the end of key range to be removed. * @param range_end is the end of key range to be removed.
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted.
*/ */
Response rmdir(std::string const& key, const char* range_end); Response rmdir(std::string const& key, const char* range_end);
@ -450,6 +454,8 @@ namespace etcd
* *
* @param key is the directory to be created to be listed * @param key is the directory to be created to be listed
* @param range_end is the end of key range to be removed. * @param range_end is the end of key range to be removed.
*
* @return Returns etcdv3::ERROR_KEY_NOT_FOUND if the no key been deleted.
*/ */
Response rmdir(std::string const& key, std::string const& range_end); Response rmdir(std::string const& key, std::string const& range_end);
@ -464,14 +470,27 @@ namespace etcd
* Gets a directory listing of the directory prefixed by the key. * Gets a directory listing of the directory prefixed by the key.
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param limit is the size limit of results to be listed, we don't use default parameters * @param limit is the size limit of results to be listed, we don't use
* to ensure backwards binary compatibility. * default parameters to ensure backwards binary compatibility. 0 means no
* limit.
*/ */
Response ls(std::string const& key, size_t const limit); Response ls(std::string const& key, size_t const limit);
/** /**
* Gets a directory listing of the directory identified by the key and range_end, i.e., get * Gets a directory listing of the directory prefixed by the key with given
* all keys in the range [key, range_end). * revision.
*
* @param key is the key to be listed
* @param limit is the size limit of results to be listed, we don't use
* default parameters to ensure backwards binary compatibility. 0 means no
* limit.
* @param revision is the revision to be listed
*/
Response ls(std::string const& key, size_t const limit, int64_t revision);
/**
* Gets a directory listing of the directory identified by the key and
* range_end, i.e., get all keys in the range [key, range_end).
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param range_end is the end of key range to be listed * @param range_end is the end of key range to be listed
@ -479,15 +498,33 @@ namespace etcd
Response ls(std::string const& key, std::string const& range_end); Response ls(std::string const& key, std::string const& range_end);
/** /**
* Gets a directory listing of the directory identified by the key and range_end, i.e., get * Gets a directory listing of the directory identified by the key and
* all keys in the range [key, range_end), and respects the given limit. * range_end, i.e., get all keys in the range [key, range_end), and respects
* the given limit.
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param range_end is the end of key range to be listed * @param range_end is the end of key range to be listed
* @param limit is the size limit of results to be listed, we don't use default parameters * @param limit is the size limit of results to be listed, we don't use
* to ensure backwards binary compatibility. * default parameters to ensure backwards binary compatibility. 0 means no
* limit.
*/ */
Response ls(std::string const & key, std::string const &range_end, size_t const limit); Response ls(std::string const& key, std::string const& range_end,
size_t const limit);
/**
* Gets a directory listing of the directory identified by the key and
* range_end, i.e., get all keys in the range [key, range_end), and respects
* the given limit and revision.
*
* @param key is the key to be listed
* @param range_end is the end of key range to be listed
* @param limit is the size limit of results to be listed, we don't use
* default parameters to ensure backwards binary compatibility. 0 means no
* limit.
* @param revision is the revision to be listed
*/
Response ls(std::string const& key, std::string const& range_end,
size_t const limit, int64_t revision);
/** /**
* Gets a directory listing of the directory prefixed by the key. * Gets a directory listing of the directory prefixed by the key.
@ -504,14 +541,27 @@ namespace etcd
* Note that only keys are included in the response. * Note that only keys are included in the response.
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param limit is the size limit of results to be listed, we don't use default parameters * @param limit is the size limit of results to be listed, we don't use
* to ensure backwards binary compatibility. * default parameters to ensure backwards binary compatibility.
*/ */
Response keys(std::string const& key, size_t const limit); Response keys(std::string const& key, size_t const limit);
/** /**
* List keys identified by the key and range_end, i.e., get all keys in the range [key, * Gets a directory listing of the directory prefixed by the key with
* range_end). * specified revision.
*
* Note that only keys are included in the response.
*
* @param key is the key to be listed
* @param limit is the size limit of results to be listed, we don't use
* default parameters to ensure backwards binary compatibility.
* @param revision is the revision to be listed
*/
Response keys(std::string const& key, size_t const limit, int64_t revision);
/**
* List keys identified by the key and range_end, i.e., get all keys in the
* range [key, range_end).
* *
* Note that only keys are included in the response. * Note that only keys are included in the response.
* *
@ -521,40 +571,61 @@ namespace etcd
Response keys(std::string const& key, std::string const& range_end); Response keys(std::string const& key, std::string const& range_end);
/** /**
* List keys identified by the key and range_end, i.e., get all keys in the range [key, * List keys identified by the key and range_end, i.e., get all keys in the
* range_end), and respects the given limit. * range [key, range_end), and respects the given limit.
* *
* Note that only keys are included in the response. * Note that only keys are included in the response.
* *
* @param key is the key to be listed * @param key is the key to be listed
* @param range_end is the end of key range to be listed * @param range_end is the end of key range to be listed
* @param limit is the size limit of results to be listed, we don't use default parameters * @param limit is the size limit of results to be listed, we don't use
* to ensure backwards binary compatibility. * default parameters to ensure backwards binary compatibility.
*/ */
Response keys(std::string const & key, std::string const &range_end, size_t const limit); Response keys(std::string const& key, std::string const& range_end,
size_t const limit);
/** /**
* Watches for changes of a key or a subtree. Please note that if you watch e.g. "/testdir" and * List keys identified by the key and range_end, i.e., get all keys in the
* a new key is created, like "/testdir/newkey" then no change happened in the value of * range [key, range_end), and respects the given limit and revision.
* "/testdir" so your watch will not detect this. If you want to detect addition and deletion of *
* directory entries then you have to do a recursive watch. * Note that only keys are included in the response.
*
* @param key is the key to be listed
* @param range_end is the end of key range to be listed
* @param limit is the size limit of results to be listed, we don't use
* default parameters to ensure backwards binary compatibility.
* @param revision is the revision to be listed
*/
Response keys(std::string const& key, std::string const& range_end,
size_t const limit, int64_t revision);
/**
* Watches for changes of a key or a subtree. Please note that if you watch
* e.g. "/testdir" and a new key is created, like "/testdir/newkey" then no
* change happened in the value of
* "/testdir" so your watch will not detect this. If you want to detect
* addition and deletion of directory entries then you have to do a recursive
* watch.
* @param key is the value or directory to be watched * @param key is the value or directory to be watched
* @param recursive if true watch a whole subtree * @param recursive if true watch a whole subtree
*/ */
Response watch(std::string const& key, bool recursive = false); Response watch(std::string const& key, bool recursive = false);
/** /**
* Watches for changes of a key or a subtree from a specific index. The index value can be in the "past". * Watches for changes of a key or a subtree from a specific index. The index
* value can be in the "past".
* @param key is the value or directory to be watched * @param key is the value or directory to be watched
* @param fromIndex the first index we are interested in * @param fromIndex the first index we are interested in
* @param recursive if true watch a whole subtree * @param recursive if true watch a whole subtree
*/ */
Response watch(std::string const & key, int64_t fromIndex, bool recursive = false); Response watch(std::string const& key, int64_t fromIndex,
bool recursive = false);
/** /**
* Watches for changes of a range of keys inside [key, range_end). * Watches for changes of a range of keys inside [key, range_end).
* *
* This overload for `const char *` is to avoid const char * to bool implicit casting. * This overload for `const char *` is to avoid const char * to bool implicit
* casting.
* *
* @param key is the value or directory to be watched * @param key is the value or directory to be watched
* @param range_end is the end of key range to be removed. * @param range_end is the end of key range to be removed.
@ -570,15 +641,17 @@ namespace etcd
Response watch(std::string const& key, std::string const& range_end); Response watch(std::string const& key, std::string const& range_end);
/** /**
* Watches for changes of a range of keys inside [key, range_end) from a specific index. The index value * Watches for changes of a range of keys inside [key, range_end) from a
* can be in the "past". * specific index. The index value can be in the "past".
* *
* Watches for changes of a key or a subtree from a specific index. The index value can be in the "past". * Watches for changes of a key or a subtree from a specific index. The index
* value can be in the "past".
* @param key is the value or directory to be watched * @param key is the value or directory to be watched
* @param range_end is the end of key range to be removed. * @param range_end is the end of key range to be removed.
* @param fromIndex the first index we are interested in * @param fromIndex the first index we are interested in
*/ */
Response watch(std::string const & key, std::string const &range_end, int64_t fromIndex); Response watch(std::string const& key, std::string const& range_end,
int64_t fromIndex);
/** /**
* Grants a lease. * Grants a lease.
@ -610,23 +683,43 @@ namespace etcd
Response leases(); Response leases();
/** /**
* Gains a lock at a key, using a default created lease, using the default lease (10 seconds), with * Add an etcd member to the etcd cluster, equivalent to `etcdctl member add`.
* keeping alive has already been taken care of by the library. * @param peer_urls is comma separated list of URLs for the new member.
* @param is_learner is true if the added member is a learner.
*/
Response add_member(std::string const& peer_urls, bool is_learner);
/**
* List all members, equivalent to `etcdctl member list`.
*/
Response list_member();
/**
* Remove a member of an etcd cluster, equivalent to `etcdctl member remove`.
* @param member_id is the id of the member to be removed.
*/
Response remove_member(uint64_t member_id);
/**
* Gains a lock at a key, using a default created lease, using the default
* lease (10 seconds), with keeping alive has already been taken care of by
* the library.
* @param key is the key to be used to request the lock. * @param key is the key to be used to request the lock.
*/ */
Response lock(std::string const& key); Response lock(std::string const& key);
/** /**
* Gains a lock at a key, using a default created lease, using the specified lease TTL (in seconds), with * Gains a lock at a key, using a default created lease, using the specified
* keeping alive has already been taken care of by the library. * lease TTL (in seconds), with keeping alive has already been taken care of
* by the library.
* @param key is the key to be used to request the lock. * @param key is the key to be used to request the lock.
* @param lease_ttl is the TTL used to create a lease for the key. * @param lease_ttl is the TTL used to create a lease for the key.
*/ */
Response lock(std::string const& key, int lease_ttl); Response lock(std::string const& key, int lease_ttl);
/** /**
* Gains a lock at a key, using a user-provided lease, the lifetime of the lease won't be taken care * Gains a lock at a key, using a user-provided lease, the lifetime of the
* of by the library. * lease won't be taken care of by the library.
* @param key is the key to be used to request the lock. * @param key is the key to be used to request the lock.
*/ */
Response lock_with_lease(std::string const& key, int64_t lease_id); Response lock_with_lease(std::string const& key, int64_t lease_id);
@ -657,7 +750,8 @@ namespace etcd
* - created rev: the revision of the generated key * - created rev: the revision of the generated key
* - lease: the lease id of the election leader * - lease: the lease id of the election leader
*/ */
Response campaign(std::string const &name, int64_t lease_id, std::string const &value); Response campaign(std::string const& name, int64_t lease_id,
std::string const& value);
/** /**
* Updates the value of election with a new value, with leader key returns by * Updates the value of election with a new value, with leader key returns by
@ -670,7 +764,8 @@ namespace etcd
* @param value is the new value to set. * @param value is the new value to set.
*/ */
Response proclaim(std::string const& name, int64_t lease_id, Response proclaim(std::string const& name, int64_t lease_id,
std::string const &key, int64_t revision, std::string const &value); std::string const& key, int64_t revision,
std::string const& value);
/** /**
* Get the current leader proclamation. * Get the current leader proclamation.
@ -690,6 +785,7 @@ namespace etcd
~Observer(); ~Observer();
// wait at least *one* response from the observer. // wait at least *one* response from the observer.
Response WaitOnce(); Response WaitOnce();
private: private:
std::shared_ptr<etcdv3::AsyncObserveAction> action = nullptr; std::shared_ptr<etcdv3::AsyncObserveAction> action = nullptr;
@ -701,7 +797,8 @@ namespace etcd
* *
* @param name is the names of election to watch. * @param name is the names of election to watch.
* *
* @returns an observer that holds that action and will cancel the request when being destructed. * @returns an observer that holds that action and will cancel the request
* when being destructed.
*/ */
std::unique_ptr<Observer> observe(std::string const& name); std::unique_ptr<Observer> observe(std::string const& name);
@ -720,34 +817,68 @@ namespace etcd
private: private:
// TODO: use std::unique_ptr<> // TODO: use std::unique_ptr<>
std::shared_ptr<etcdv3::AsyncHeadAction> head_internal(); std::shared_ptr<etcdv3::AsyncHeadAction> head_internal();
std::shared_ptr<etcdv3::AsyncRangeAction> get_internal(std::string const & key); std::shared_ptr<etcdv3::AsyncRangeAction> get_internal(
std::shared_ptr<etcdv3::AsyncSetAction> set_internal(std::string const & key, std::string const & value, int64_t leaseId); std::string const& key, const int64_t revision = 0);
std::shared_ptr<etcdv3::AsyncSetAction> add_internal(std::string const & key, std::string const & value, int64_t leaseId); std::shared_ptr<etcdv3::AsyncSetAction> add_internal(
std::shared_ptr<etcdv3::AsyncPutAction> put_internal(std::string const & key, std::string const & value); std::string const& key, std::string const& value,
std::shared_ptr<etcdv3::AsyncUpdateAction> modify_internal(std::string const & key, std::string const & value, int64_t leaseId); const int64_t leaseId = 0);
std::shared_ptr<etcdv3::AsyncCompareAndSwapAction> modify_if_internal(std::string const & key, std::string const & value, int64_t old_index, std::string const & old_value, int64_t leaseId, etcdv3::AtomicityType const & atomicity_type); std::shared_ptr<etcdv3::AsyncPutAction> put_internal(
std::shared_ptr<etcdv3::AsyncDeleteAction> rm_internal(std::string const & key); std::string const& key, std::string const& value,
std::shared_ptr<etcdv3::AsyncCompareAndDeleteAction> rm_if_internal(std::string const & key, int64_t old_index, const std::string & old_value, etcdv3::AtomicityType const & atomicity_type); const int64_t leaseId = 0);
std::shared_ptr<etcdv3::AsyncDeleteAction> rmdir_internal(std::string const & key, bool recursive = false); std::shared_ptr<etcdv3::AsyncUpdateAction> modify_internal(
std::shared_ptr<etcdv3::AsyncDeleteAction> rmdir_internal(std::string const & key, std::string const &range_end); std::string const& key, std::string const& value,
std::shared_ptr<etcdv3::AsyncRangeAction> ls_internal(std::string const & key, size_t const limit, bool const keys_only = false); const int64_t leaseId = 0);
std::shared_ptr<etcdv3::AsyncRangeAction> ls_internal(std::string const & key, std::string const &range_end, size_t const limit, bool const keys_only = false); std::shared_ptr<etcdv3::AsyncCompareAndSwapAction> modify_if_internal(
std::shared_ptr<etcdv3::AsyncWatchAction> watch_internal(std::string const & key, int64_t fromIndex, bool recursive = false); std::string const& key, std::string const& value, int64_t old_index,
std::shared_ptr<etcdv3::AsyncWatchAction> watch_internal(std::string const & key, std::string const &range_end, int64_t fromIndex); std::string const& old_value, etcdv3::AtomicityType const& atomicity_type,
std::shared_ptr<etcdv3::AsyncLeaseRevokeAction> leaserevoke_internal(int64_t lease_id); const int64_t leaseId = 0);
std::shared_ptr<etcdv3::AsyncLeaseTimeToLiveAction> leasetimetolive_internal(int64_t lease_id); std::shared_ptr<etcdv3::AsyncDeleteAction> rm_internal(
std::string const& key);
std::shared_ptr<etcdv3::AsyncCompareAndDeleteAction> rm_if_internal(
std::string const& key, int64_t old_index, const std::string& old_value,
etcdv3::AtomicityType const& atomicity_type);
std::shared_ptr<etcdv3::AsyncDeleteAction> rmdir_internal(
std::string const& key, bool recursive = false);
std::shared_ptr<etcdv3::AsyncDeleteAction> rmdir_internal(
std::string const& key, std::string const& range_end);
std::shared_ptr<etcdv3::AsyncRangeAction> ls_internal(
std::string const& key, size_t const limit, bool const keys_only = false,
int64_t revision = 0);
std::shared_ptr<etcdv3::AsyncRangeAction> ls_internal(
std::string const& key, std::string const& range_end, size_t const limit,
bool const keys_only = false, int64_t revision = 0);
std::shared_ptr<etcdv3::AsyncWatchAction> watch_internal(
std::string const& key, int64_t fromIndex, bool recursive = false);
std::shared_ptr<etcdv3::AsyncWatchAction> watch_internal(
std::string const& key, std::string const& range_end, int64_t fromIndex);
std::shared_ptr<etcdv3::AsyncLeaseRevokeAction> leaserevoke_internal(
int64_t lease_id);
std::shared_ptr<etcdv3::AsyncLeaseTimeToLiveAction> leasetimetolive_internal(
int64_t lease_id);
std::shared_ptr<etcdv3::AsyncLeaseLeasesAction> leases_internal(); std::shared_ptr<etcdv3::AsyncLeaseLeasesAction> leases_internal();
Response lock_internal(std::string const &key, std::shared_ptr<etcd::KeepAlive> const &keepalive); std::shared_ptr<etcdv3::AsyncAddMemberAction> add_member_internal(
std::shared_ptr<etcdv3::AsyncLockAction> lock_with_lease_internal(std::string const &key, int64_t lease_id); std::string const& peer_urls, bool is_learner);
std::shared_ptr<etcdv3::AsyncUnlockAction> unlock_internal(std::string const &lock_key); std::shared_ptr<etcdv3::AsyncListMemberAction> list_member_internal();
std::shared_ptr<etcdv3::AsyncTxnAction> txn_internal(etcdv3::Transaction const &txn); std::shared_ptr<etcdv3::AsyncRemoveMemberAction> remove_member_internal(
std::shared_ptr<etcdv3::AsyncCampaignAction> campaign_internal(std::string const &name, int64_t lease_id, std::string const &value); const uint64_t member_id);
Response lock_internal(std::string const& key,
std::shared_ptr<etcd::KeepAlive> const& keepalive);
std::shared_ptr<etcdv3::AsyncLockAction> lock_with_lease_internal(
std::string const& key, int64_t lease_id);
std::shared_ptr<etcdv3::AsyncUnlockAction> unlock_internal(
std::string const& lock_key);
std::shared_ptr<etcdv3::AsyncTxnAction> txn_internal(
etcdv3::Transaction const& txn);
std::shared_ptr<etcdv3::AsyncCampaignAction> campaign_internal(
std::string const& name, int64_t lease_id, std::string const& value);
std::shared_ptr<etcdv3::AsyncProclaimAction> proclaim_internal( std::shared_ptr<etcdv3::AsyncProclaimAction> proclaim_internal(
std::string const &name, int64_t lease_id, std::string const& name, int64_t lease_id, std::string const& key,
std::string const &key, int64_t revision, std::string const &value); int64_t revision, std::string const& value);
std::shared_ptr<etcdv3::AsyncLeaderAction> leader_internal(std::string const &name); std::shared_ptr<etcdv3::AsyncLeaderAction> leader_internal(
std::shared_ptr<etcdv3::AsyncResignAction> resign_internal(std::string const &name, int64_t lease_id, std::string const& name);
std::string const &key, int64_t revision); std::shared_ptr<etcdv3::AsyncResignAction> resign_internal(
std::string const& name, int64_t lease_id, std::string const& key,
int64_t revision);
public: public:
/** /**
@ -768,8 +899,10 @@ namespace etcd
* Set a timeout value for grpc operations. * Set a timeout value for grpc operations.
*/ */
template <typename Period = std::micro> template <typename Period = std::micro>
void set_grpc_timeout(std::chrono::duration<std::chrono::microseconds::rep, Period> const &timeout) { void set_grpc_timeout(std::chrono::duration<std::chrono::microseconds::rep,
grpc_timeout = std::chrono::duration_cast<std::chrono::milliseconds>(timeout); Period> const& timeout) {
grpc_timeout =
std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
} }
/** /**
@ -786,8 +919,10 @@ namespace etcd
std::shared_ptr<grpc_impl::Channel> channel; std::shared_ptr<grpc_impl::Channel> channel;
#endif #endif
mutable std::unique_ptr<TokenAuthenticator, TokenAuthenticatorDeleter> token_authenticator; mutable std::unique_ptr<TokenAuthenticator, TokenAuthenticatorDeleter>
mutable std::chrono::microseconds grpc_timeout = std::chrono::microseconds::zero(); token_authenticator;
mutable std::chrono::microseconds grpc_timeout =
std::chrono::microseconds::zero();
struct EtcdServerStubs; struct EtcdServerStubs;
struct EtcdServerStubsDeleter { struct EtcdServerStubsDeleter {
@ -804,6 +939,6 @@ namespace etcd
friend class Client; friend class Client;
}; };
} } // namespace etcd
#endif #endif

View File

@ -11,14 +11,13 @@ namespace etcdv3 {
namespace mvccpb { namespace mvccpb {
class KeyValue; class KeyValue;
class Event; class Event;
} } // namespace mvccpb
namespace electionpb { namespace electionpb {
class LeaderKey; class LeaderKey;
} }
namespace etcd namespace etcd {
{
class Value; class Value;
class Event; class Event;
class Response; class Response;
@ -28,12 +27,11 @@ namespace etcd
/** /**
* Represents a value object received from the etcd server * Represents a value object received from the etcd server
*/ */
class Value class Value {
{
public: public:
/** /**
* Returns true if this value represents a directory on the server. If true the as_string() * Returns true if this value represents a directory on the server. If true
* method is meaningless. * the as_string() method is meaningless.
*/ */
bool is_dir() const; bool is_dir() const;
@ -73,7 +71,8 @@ namespace etcd
friend class Client; friend class Client;
friend class SyncClient; friend class SyncClient;
friend class Response; friend class Response;
friend class BaseResponse; //deliberately done since Value class will be removed during full V3 friend class BaseResponse; // deliberately done since Value class will be
// removed during full V3
friend class DeleteRpcResponse; friend class DeleteRpcResponse;
friend class AsyncDeleteResponse; friend class AsyncDeleteResponse;
@ -92,10 +91,11 @@ namespace etcd
int64_t leaseId; int64_t leaseId;
}; };
typedef std::vector<Value> Values; using Values = std::vector<Value>;
class Event std::ostream& operator<<(std::ostream& os, const Value& value);
{
class Event {
public: public:
enum class EventType { enum class EventType {
PUT, PUT,
@ -124,7 +124,12 @@ namespace etcd
bool _has_kv, _has_prev_kv; bool _has_kv, _has_prev_kv;
}; };
typedef std::vector<Event> Events; 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

@ -8,32 +8,24 @@
#include "etcd/Response.hpp" #include "etcd/Response.hpp"
namespace etcd namespace etcd {
{
// forward declaration to avoid header/library dependency // forward declaration to avoid header/library dependency
class Client; class Client;
class Watcher class Watcher {
{
public: public:
Watcher(Client const& client, std::string const& key, Watcher(Client const& client, std::string const& key,
std::function<void(Response)> callback, std::function<void(Response)> callback, bool recursive = false);
bool recursive=false);
Watcher(SyncClient const& client, std::string const& key, Watcher(SyncClient const& client, std::string const& key,
std::function<void(Response)> callback, std::function<void(Response)> callback, bool recursive = false);
bool recursive=false);
Watcher(Client const& client, std::string const& key, Watcher(Client 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(SyncClient const& client, std::string const& key, 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 const& client, std::string const& key, int64_t fromIndex, Watcher(Client const& client, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback, bool recursive = false);
bool recursive=false);
Watcher(SyncClient const& client, std::string const& key, int64_t fromIndex, Watcher(SyncClient const& client, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback, bool recursive = false);
bool recursive=false);
Watcher(Client const& client, std::string const& key, Watcher(Client const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback); std::function<void(Response)> callback);
@ -41,78 +33,60 @@ namespace etcd
std::string const& range_end, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback); std::function<void(Response)> callback);
Watcher(std::string const& address, std::string const& key, Watcher(std::string const& address, std::string const& key,
std::function<void(Response)> callback, std::function<void(Response)> callback, bool recursive = false);
bool recursive=false);
Watcher(std::string const& address, std::string const& key, 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(std::string const& address, std::string const& key, int64_t fromIndex, Watcher(std::string const& address, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback, bool recursive = false);
bool recursive=false);
Watcher(std::string const& address, std::string const& key, Watcher(std::string const& address, std::string const& key,
std::string const& range_end, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback); std::function<void(Response)> callback);
Watcher(std::string const & address, 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, bool recursive = false,
std::function<void(Response)> callback,
bool recursive=false,
int const auth_token_ttl = 300); int const auth_token_ttl = 300);
Watcher(std::string const & address, 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,
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, std::function<void(Response)> callback,
int const auth_token_ttl = 300); int const auth_token_ttl = 300);
Watcher(std::string const & address, Watcher(std::string const& address, std::string const& ca,
std::string const & username, std::string const & password, std::string const& cert, std::string const& privkey,
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::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, bool recursive = false, std::function<void(Response)> callback, bool recursive = false,
std::string const& target_name_override = ""); std::string const& target_name_override = "");
Watcher(std::string const & address, Watcher(std::string const& address, std::string const& ca,
std::string const & ca, std::string const& cert, std::string const& privkey,
std::string const & cert, std::string const& key, std::string const& range_end,
std::string const & privkey, int64_t fromIndex, std::function<void(Response)> callback,
std::string const & key, std::string const &range_end, int64_t fromIndex,
std::function<void(Response)> callback,
std::string const& target_name_override = ""); std::string const& target_name_override = "");
Watcher(Client const& client, std::string const& key, Watcher(Client const& client, std::string const& key,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive = false);
bool recursive=false);
Watcher(SyncClient const& client, std::string const& key, Watcher(SyncClient const& client, std::string const& key,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive = false);
bool recursive=false);
Watcher(Client const& client, std::string const& key, Watcher(Client 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,
std::function<void(bool)> wait_callback); std::function<void(bool)> wait_callback);
Watcher(SyncClient const& client, std::string const& key, 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,
std::function<void(bool)> wait_callback); std::function<void(bool)> wait_callback);
Watcher(Client const& client, std::string const& key, int64_t fromIndex, Watcher(Client const& client, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive = false);
bool recursive=false);
Watcher(SyncClient const& client, std::string const& key, int64_t fromIndex, Watcher(SyncClient const& client, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive = false);
bool recursive=false);
Watcher(Client const& client, std::string const& key, Watcher(Client const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
@ -123,61 +97,48 @@ namespace etcd
std::function<void(bool)> wait_callback); std::function<void(bool)> wait_callback);
Watcher(std::string const& address, std::string const& key, Watcher(std::string const& address, std::string const& key,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive = false);
bool recursive=false);
Watcher(std::string const& address, std::string const& key, 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,
std::function<void(bool)> wait_callback); std::function<void(bool)> wait_callback);
Watcher(std::string const& address, std::string const& key, int64_t fromIndex, Watcher(std::string const& address, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive = false);
bool recursive=false);
Watcher(std::string const& address, std::string const& key, Watcher(std::string const& address, std::string const& key,
std::string const& range_end, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback); std::function<void(bool)> wait_callback);
Watcher(std::string const & address, 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,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive = false,
bool recursive=false,
int const auth_token_ttl = 300); int const auth_token_ttl = 300);
Watcher(std::string const & address, 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(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(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback,
int const auth_token_ttl = 300); int const auth_token_ttl = 300);
Watcher(std::string const & address, Watcher(std::string const& address, std::string const& ca,
std::string const & username, std::string const & password, std::string const& cert, std::string const& privkey,
std::string const& key, int64_t fromIndex, std::string const& key, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive = false,
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 = ""); std::string const& target_name_override = "");
Watcher(std::string const & address, Watcher(std::string const& address, std::string const& ca,
std::string const & ca, std::string const& cert, std::string const& privkey,
std::string const & cert, std::string const& key, std::string const& range_end,
std::string const & privkey, int64_t fromIndex, std::function<void(Response)> callback,
std::string const & key, std::string const &range_end, int64_t fromIndex,
std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback,
std::string const& target_name_override = ""); std::string const& target_name_override = "");
@ -185,19 +146,27 @@ namespace etcd
Watcher(Watcher&&) = delete; Watcher(Watcher&&) = delete;
/** /**
* Wait util the task has been stopped, actively or passively, e.g., the watcher * Wait util the task has been stopped, actively or passively, e.g., the
* get cancelled or the server closes the connection. * watcher get cancelled or the server closes the connection.
* *
* Returns true if the watcher is been normally cancalled, otherwise false. * Returns true if the watcher is been normally cancelled, otherwise false.
*/ */
bool Wait(); bool Wait();
/** /**
* An async wait, the callback will be called when the task has been stopped. * 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 cancalled. * 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).
*/ */
void Wait(std::function<void(bool)> callback); bool Wait(std::function<void(bool)> callback);
/** /**
* Stop the watching action. * Stop the watching action.
@ -212,16 +181,16 @@ namespace etcd
~Watcher(); ~Watcher();
protected: protected:
void doWatch(std::string const & key, void doWatch(std::string const& key, std::string const& range_end,
std::string const & range_end,
std::string const& auth_token, std::string const& auth_token,
std::function<void(Response)> callback); std::function<void(Response)> callback);
std::function<void(Response)> callback; std::function<void(Response)> callback;
std::function<void(bool)> wait_callback; std::function<void(bool)> wait_callback;
// Don't use `pplx::task` to avoid sharing thread pool with other actions on the client // Don't use `pplx::task` to avoid sharing thread pool with other actions on
// to avoid any potential blocking, which may block the keepalive loop and evict the lease. // the client to avoid any potential blocking, which may block the keepalive
// loop and evict the lease.
std::thread task_; std::thread task_;
struct EtcdServerStubs; struct EtcdServerStubs;
@ -235,6 +204,6 @@ namespace etcd
bool recursive; bool recursive;
std::atomic_bool cancelled; std::atomic_bool cancelled;
}; };
} } // namespace etcd
#endif #endif

View File

@ -6,37 +6,34 @@
#include <grpc++/grpc++.h> #include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h" #include "proto/rpc.grpc.pb.h"
#include "proto/v3lock.grpc.pb.h"
#include "proto/v3election.grpc.pb.h" #include "proto/v3election.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 v3lockpb::Lock; using etcdserverpb::Watch;
using v3electionpb::Election; using v3electionpb::Election;
using v3lockpb::Lock;
namespace etcd { namespace etcd {
class Response; class Response;
} }
namespace etcdv3 namespace etcdv3 {
{ enum class AtomicityType { PREV_INDEX = 0, PREV_VALUE = 1 };
enum class AtomicityType
{
PREV_INDEX = 0,
PREV_VALUE = 1
};
struct ActionParameters struct ActionParameters {
{
ActionParameters(); ActionParameters();
bool withPrefix; bool withPrefix;
int64_t revision; int64_t revision = 0;
int64_t old_revision; int64_t old_revision = 0;
int64_t lease_id = 0; // no lease int64_t lease_id = 0; // no lease
int ttl; int ttl;
int limit; int limit;
@ -48,9 +45,16 @@ namespace etcdv3
std::string value; std::string value;
std::string old_value; std::string old_value;
std::string auth_token; std::string auth_token;
// for cluster management apis
std::vector<std::string> peer_urls;
bool is_learner;
uint64_t member_id;
std::chrono::microseconds grpc_timeout = std::chrono::microseconds::zero(); std::chrono::microseconds grpc_timeout = std::chrono::microseconds::zero();
KV::Stub* kv_stub; KV::Stub* kv_stub;
Watch::Stub* watch_stub; Watch::Stub* watch_stub;
Cluster::Stub* cluster_stub;
Lease::Stub* lease_stub; Lease::Stub* lease_stub;
Lock::Stub* lock_stub; Lock::Stub* lock_stub;
Election::Stub* election_stub; Election::Stub* election_stub;
@ -61,19 +65,22 @@ namespace etcdv3
void dump(std::ostream& os) const; void dump(std::ostream& os) const;
}; };
class Action class Action {
{
public: public:
Action(etcdv3::ActionParameters const& params); Action(etcdv3::ActionParameters const& params);
Action(etcdv3::ActionParameters&& params); Action(etcdv3::ActionParameters&& params);
virtual ~Action();
void waitForResponse(); void waitForResponse();
const std::chrono::high_resolution_clock::time_point startTimepoint(); const std::chrono::high_resolution_clock::time_point startTimepoint();
protected: protected:
Status status; Status status;
ClientContext context; ClientContext context;
CompletionQueue cq_; CompletionQueue cq_;
etcdv3::ActionParameters parameters; etcdv3::ActionParameters parameters;
std::chrono::high_resolution_clock::time_point start_timepoint; std::chrono::high_resolution_clock::time_point start_timepoint;
private: private:
// Init things like auth token, etc. // Init things like auth token, etc.
void InitAction(); void InitAction();
@ -83,6 +90,28 @@ namespace etcdv3
namespace detail { namespace detail {
std::string string_plus_one(std::string const& value); 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 && params, etcdv3::AtomicityType 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 && params, etcdv3::AtomicityType 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/AsyncDeleteResponse.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::DeleteRangeResponse;
namespace etcdv3
{
class AsyncDeleteAction : public etcdv3::Action
{
public:
AsyncDeleteAction(etcdv3::ActionParameters && params);
AsyncDeleteResponse 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 AsyncDeleteResponse : public etcdv3::V3Response
{
public:
AsyncDeleteResponse(){};
void ParseResponse(std::string const& key, bool prefix, DeleteRangeResponse& resp);
};
}
#endif

View File

@ -1,80 +0,0 @@
#ifndef __ASYNC_ELECTIONACTION_HPP__
#define __ASYNC_ELECTIONACTION_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "proto/v3election.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncElectionResponse.hpp"
#include "etcd/Response.hpp"
using grpc::ClientAsyncResponseReader;
using grpc::ClientAsyncReader;
using v3electionpb::CampaignRequest;
using v3electionpb::CampaignResponse;
using v3electionpb::ProclaimRequest;
using v3electionpb::ProclaimResponse;
using v3electionpb::LeaderRequest;
using v3electionpb::LeaderResponse;
using v3electionpb::ResignRequest;
using v3electionpb::ResignResponse;
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 AsyncProclaimAction : public etcdv3::Action
{
public:
AsyncProclaimAction(etcdv3::ActionParameters && params);
AsyncProclaimResponse ParseResponse();
private:
ProclaimResponse reply;
std::unique_ptr<ClientAsyncResponseReader<ProclaimResponse>> 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 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_cancalled;
};
class AsyncResignAction : public etcdv3::Action
{
public:
AsyncResignAction(etcdv3::ActionParameters && params);
AsyncResignResponse ParseResponse();
private:
ResignResponse reply;
std::unique_ptr<ClientAsyncResponseReader<ResignResponse>> response_reader;
};
}
#endif

View File

@ -1,51 +0,0 @@
#ifndef __ASYNC_ELECTIONRESPONSE_HPP__
#define __ASYNC_ELECTIONRESPONSE_HPP__
#include "proto/rpc.pb.h"
#include "proto/v3election.pb.h"
#include "etcd/v3/V3Response.hpp"
using v3electionpb::CampaignResponse;
using v3electionpb::ProclaimResponse;
using v3electionpb::LeaderResponse;
using v3electionpb::ResignResponse;
namespace etcdv3
{
class AsyncCampaignResponse : public etcdv3::V3Response
{
public:
AsyncCampaignResponse(){};
void ParseResponse(CampaignResponse& resp);
};
class AsyncProclaimResponse : public etcdv3::V3Response
{
public:
AsyncProclaimResponse(){};
void ParseResponse(ProclaimResponse& resp);
};
class AsyncLeaderResponse : public etcdv3::V3Response
{
public:
AsyncLeaderResponse(){};
void ParseResponse(LeaderResponse& resp);
};
class AsyncObserveResponse : public etcdv3::V3Response
{
public:
AsyncObserveResponse(){};
void ParseResponse(LeaderResponse& resp);
};
class AsyncResignResponse : public etcdv3::V3Response
{
public:
AsyncResignResponse(){};
void ParseResponse(ResignResponse& 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_HEAD_HPP__
#define __ASYNC_HEAD_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncHeadResponse.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::RangeResponse;
namespace etcdv3
{
class AsyncHeadAction : public etcdv3::Action
{
public:
AsyncHeadAction(etcdv3::ActionParameters && params);
AsyncHeadResponse ParseResponse();
private:
RangeResponse reply;
std::unique_ptr<ClientAsyncResponseReader<RangeResponse>> response_reader;
};
}
#endif

View File

@ -1,22 +0,0 @@
#ifndef __ASYNC_HEADRESPONSE_HPP__
#define __ASYNC_HEADRESPONSE_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 AsyncHeadResponse : public etcdv3::V3Response
{
public:
AsyncHeadResponse(){};
void ParseResponse(RangeResponse& resp);
};
}
#endif

View File

@ -1,89 +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 etcd {
class KeepAlive;
}
namespace etcdv3
{
class AsyncLeaseGrantAction : public etcdv3::Action {
public:
AsyncLeaseGrantAction(etcdv3::ActionParameters && params);
AsyncLeaseGrantResponse ParseResponse();
private:
LeaseGrantResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaseGrantResponse>> 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 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;
bool isCancelled;
std::mutex protect_is_cancelled;
friend class etcd::KeepAlive;
};
class AsyncLeaseTimeToLiveAction: public etcdv3::Action {
public:
AsyncLeaseTimeToLiveAction(etcdv3::ActionParameters && params);
AsyncLeaseTimeToLiveResponse ParseResponse();
private:
LeaseTimeToLiveResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LeaseTimeToLiveResponse>> response_reader;
};
class AsyncLeaseLeasesAction: public etcdv3::Action {
public:
AsyncLeaseLeasesAction(etcdv3::ActionParameters && params);
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,40 +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 && params);
AsyncLockResponse ParseResponse();
private:
LockResponse reply;
std::unique_ptr<ClientAsyncResponseReader<LockResponse>> response_reader;
};
class AsyncUnlockAction : public etcdv3::Action
{
public:
AsyncUnlockAction(etcdv3::ActionParameters && params);
AsyncUnlockResponse ParseResponse();
private:
UnlockResponse reply;
std::unique_ptr<ClientAsyncResponseReader<UnlockResponse>> response_reader;
};
}
#endif

View File

@ -1,32 +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,26 +0,0 @@
#ifndef __ASYNC_PUT_HPP__
#define __ASYNC_PUT_HPP__
#include <grpc++/grpc++.h>
#include "proto/rpc.grpc.pb.h"
#include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncPutResponse.hpp"
using grpc::ClientAsyncResponseReader;
using etcdserverpb::PutResponse;
namespace etcdv3
{
class AsyncPutAction : public etcdv3::Action
{
public:
AsyncPutAction(etcdv3::ActionParameters && params);
AsyncPutResponse ParseResponse();
private:
PutResponse reply;
std::unique_ptr<ClientAsyncResponseReader<PutResponse>> response_reader;
};
}
#endif

View File

@ -1,23 +0,0 @@
#ifndef __ASYNC_PUTRESPONSE_HPP__
#define __ASYNC_PUTRESPONSE_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::PutResponse;
namespace etcdv3
{
class AsyncPutResponse : public etcdv3::V3Response
{
public:
AsyncPutResponse(){};
void ParseResponse(PutResponse& resp);
};
}
#endif

View File

@ -1,26 +0,0 @@
#ifndef __ASYNC_RANGE_HPP__
#define __ASYNC_RANGE_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 AsyncRangeAction : public etcdv3::Action
{
public:
AsyncRangeAction(etcdv3::ActionParameters && params);
AsyncRangeResponse ParseResponse();
private:
RangeResponse reply;
std::unique_ptr<ClientAsyncResponseReader<RangeResponse>> response_reader;
};
}
#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 && params, bool create=false);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
bool isCreate;
};
}
#endif

View File

@ -1,29 +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::TxnRequest;
using etcdserverpb::TxnResponse;
using etcdserverpb::KV;
namespace etcdv3
{
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;
};
}
#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 && params);
AsyncTxnResponse ParseResponse();
private:
TxnResponse reply;
std::unique_ptr<ClientAsyncResponseReader<TxnResponse>> response_reader;
};
}
#endif

View File

@ -1,35 +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 && params);
AsyncWatchResponse ParseResponse();
void waitForResponse();
void waitForResponse(std::function<void(etcd::Response)> callback);
void CancelWatch();
bool Cancelled() const;
private:
WatchResponse reply;
std::unique_ptr<ClientAsyncReaderWriter<WatchRequest,WatchResponse>> stream;
std::atomic_bool isCancelled;
};
}
#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 {
namespace etcdv3 class KeyValue {
{
class KeyValue
{
public: public:
KeyValue(); KeyValue();
mvccpb::KeyValue kvs; mvccpb::KeyValue kvs;
void set_ttl(int ttl); void set_ttl(int ttl);
int get_ttl() const; int get_ttl() const;
private: private:
int ttl; int ttl;
}; };
} } // namespace etcdv3
#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,8 +1,8 @@
#ifndef V3_SRC_TRANSACTION_HPP_ #ifndef V3_SRC_TRANSACTION_HPP_
#define V3_SRC_TRANSACTION_HPP_ #define V3_SRC_TRANSACTION_HPP_
#include <string>
#include <memory> #include <memory>
#include <string>
namespace etcdserverpb { namespace etcdserverpb {
class TxnRequest; class TxnRequest;
@ -28,33 +28,196 @@ enum class CompareTarget {
class Transaction { class Transaction {
public: public:
Transaction(); Transaction();
Transaction(std::string const&); ~Transaction();
virtual ~Transaction();
// Set a new key for different comparisons and /put/get/delete requests. union Value {
void reset_key(std::string const& newkey); int64_t version;
int64_t create_revision;
int64_t mod_revision;
std::string value;
int64_t lease;
};
void init_compare(CompareResult, CompareTarget); void add_compare(std::string const& key, CompareTarget const& target,
void init_compare(std::string const &old_value, CompareResult, CompareTarget); CompareResult const& result, Value const& target_value,
void init_compare(int64_t old_value, CompareResult, CompareTarget); std::string const& range_end = "");
void setup_basic_failure_operation(std::string const &key); void add_compare_version(std::string const& key, int64_t const& version,
void setup_set_failure_operation(std::string const &key, std::string const &value, int64_t leaseid); std::string const& range_end = "");
void setup_basic_create_sequence(std::string const &key, std::string const &value, int64_t leaseid); void add_compare_version(std::string const& key, CompareResult const& result,
void setup_compare_and_swap_sequence(std::string const &valueToSwap, int64_t leaseid); int64_t const& version,
void setup_delete_sequence(std::string const &key, std::string const &range_end, bool recursive); std::string const& range_end = "");
void setup_delete_failure_operation(std::string const &key, std::string const &range_end, bool recursive); void add_compare_create(std::string const& key,
void setup_compare_and_delete_operation(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 // update without `get` and no `prev_kv` returned
void setup_put(std::string const& key, std::string const& value); 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);
void setup_delete(std::string const& key, std::string const& range_end,
const bool recursive = false);
std::shared_ptr<etcdserverpb::TxnRequest> txn_request; std::shared_ptr<etcdserverpb::TxnRequest> txn_request;
private:
std::string key;
}; };
} } // namespace etcdv3
#endif #endif

View File

@ -6,11 +6,10 @@
#include "proto/v3election.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: public:
V3Response() : error_code(0), index(0){}; V3Response() : error_code(0), index(0){};
void set_error_code(int code); void set_error_code(int code);
@ -26,6 +25,9 @@ namespace etcdv3
etcdv3::KeyValue const& get_prev_value() const; etcdv3::KeyValue const& get_prev_value() const;
bool has_values() const; bool has_values() const;
int64_t get_compact_revision() const; int64_t get_compact_revision() const;
void set_compact_revision(const int64_t compact_revision);
int64_t get_watch_id() const;
void set_watch_id(const int64_t watch_id);
void set_lock_key(std::string const& key); void set_lock_key(std::string const& key);
std::string const& get_lock_key() const; std::string const& get_lock_key() const;
void set_name(std::string const& name); void set_name(std::string const& name);
@ -35,6 +37,8 @@ namespace etcdv3
uint64_t get_member_id() const; uint64_t get_member_id() const;
uint64_t get_raft_term() const; uint64_t get_raft_term() const;
std::vector<int64_t> const& get_leases() const; std::vector<int64_t> const& get_leases() const;
std::vector<etcdv3::Member> const& get_members() const;
protected: protected:
int error_code; int error_code;
int64_t index; int64_t index;
@ -45,6 +49,7 @@ namespace etcdv3
std::vector<etcdv3::KeyValue> values; std::vector<etcdv3::KeyValue> values;
std::vector<etcdv3::KeyValue> prev_values; std::vector<etcdv3::KeyValue> prev_values;
int64_t compact_revision = -1; int64_t compact_revision = -1;
int64_t watch_id = -1;
std::string lock_key; // for lock std::string lock_key; // for lock
std::string name; // for campaign (in v3election) std::string name; // for campaign (in v3election)
std::vector<mvccpb::Event> events; // for watch std::vector<mvccpb::Event> events; // for watch
@ -56,6 +61,8 @@ namespace etcdv3
// for lease list // for lease list
std::vector<int64_t> leases; std::vector<int64_t> leases;
// for member list
std::vector<etcdv3::Member> members;
}; };
} } // namespace etcdv3
#endif #endif

View File

@ -1,8 +1,9 @@
#ifndef __ETCD_ACTION_CONSTANTS_HPP__ #ifndef __ETCD_ACTION_CONSTANTS_HPP__
#define __ETCD_ACTION_CONSTANTS_HPP__ #define __ETCD_ACTION_CONSTANTS_HPP__
namespace etcdv3 #include <string>
{
namespace etcdv3 {
extern char const* CREATE_ACTION; extern char const* CREATE_ACTION;
extern char const* UPDATE_ACTION; extern char const* UPDATE_ACTION;
extern char const* SET_ACTION; extern char const* SET_ACTION;
@ -22,24 +23,32 @@ namespace etcdv3
extern char const* LEASETIMETOLIVE; extern char const* LEASETIMETOLIVE;
extern char const* LEASELEASES; extern char const* LEASELEASES;
extern char const* ADDMEMBER;
extern char const* LISTMEMBER;
extern char const* REMOVEMEMBER;
extern char const* CAMPAIGN_ACTION; extern char const* CAMPAIGN_ACTION;
extern char const* PROCLAIM_ACTION; extern char const* PROCLAIM_ACTION;
extern char const* LEADER_ACTION; extern char const* LEADER_ACTION;
extern char const* OBSERVE_ACTION; extern char const* OBSERVE_ACTION;
extern char const* RESIGN_ACTION; extern char const* RESIGN_ACTION;
extern char const * NUL; extern std::string const NUL;
extern char const* KEEPALIVE_CREATE; extern char const* KEEPALIVE_CREATE;
extern char const* KEEPALIVE_WRITE; extern char const* KEEPALIVE_WRITE;
extern char const* KEEPALIVE_READ; extern char const* KEEPALIVE_READ;
extern char const* KEEPALIVE_DONE; extern char const* KEEPALIVE_DONE;
extern char const* KEEPALIVE_FINISH;
extern char const* WATCH_CREATE; extern char const* WATCH_CREATE;
extern char const* WATCH_WRITE; extern char const* WATCH_WRITE;
extern char const* WATCH_WRITE_CANCEL;
extern char const* WATCH_WRITES_DONE; extern char const* WATCH_WRITES_DONE;
extern char const* WATCH_FINISH;
extern char const* ELECTION_OBSERVE_CREATE; extern char const* ELECTION_OBSERVE_CREATE;
extern char const* ELECTION_OBSERVE_FINISH;
extern const int ERROR_GRPC_OK; extern const int ERROR_GRPC_OK;
extern const int ERROR_GRPC_CANCELLED; extern const int ERROR_GRPC_CANCELLED;
@ -63,6 +72,6 @@ namespace etcdv3
extern const int ERROR_COMPARE_FAILED; extern const int ERROR_COMPARE_FAILED;
extern const int ERROR_KEY_ALREADY_EXISTS; extern const int ERROR_KEY_ALREADY_EXISTS;
extern const int ERROR_ACTION_CANCELLED; extern const int ERROR_ACTION_CANCELLED;
} } // namespace etcdv3
#endif #endif

View File

@ -18,33 +18,61 @@ file(GLOB_RECURSE CPP_CLIENT_CORE_SRC
) )
add_library(etcd-cpp-api-core-objects OBJECT ${CPP_CLIENT_CORE_SRC} ${PROTOBUF_GENERATES}) add_library(etcd-cpp-api-core-objects OBJECT ${CPP_CLIENT_CORE_SRC} ${PROTOBUF_GENERATES})
use_cxx11(etcd-cpp-api-core-objects) use_cxx(etcd-cpp-api-core-objects)
set_exceptions(etcd-cpp-api-core-objects)
add_dependencies(etcd-cpp-api-core-objects protobuf_generates) add_dependencies(etcd-cpp-api-core-objects protobuf_generates)
include_generated_protobuf_files(etcd-cpp-api-core-objects) include_generated_protobuf_files(etcd-cpp-api-core-objects)
target_link_libraries(etcd-cpp-api-core-objects PUBLIC
# add the core library, includes the sycnhronous client only
add_library(etcd-cpp-api-core $<TARGET_OBJECTS:etcd-cpp-api-core-objects>)
use_cxx11(etcd-cpp-api-core)
target_link_libraries(etcd-cpp-api-core PUBLIC
${Boost_LIBRARIES}
${PROTOBUF_LIBRARIES}
${OPENSSL_LIBRARIES} ${OPENSSL_LIBRARIES}
${GRPC_LIBRARIES} ${GRPC_LIBRARIES}
) )
include_generated_protobuf_files(etcd-cpp-api-core) 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()
if(BUILD_ETCD_CORE_ONLY)
# 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 the client with asynchronus client
add_library(etcd-cpp-api $<TARGET_OBJECTS:etcd-cpp-api-core-objects> add_library(etcd-cpp-api $<TARGET_OBJECTS:etcd-cpp-api-core-objects>
"${CMAKE_CURRENT_SOURCE_DIR}/Client.cpp") "${CMAKE_CURRENT_SOURCE_DIR}/Client.cpp")
use_cxx11(etcd-cpp-api) use_cxx(etcd-cpp-api)
set_exceptions(etcd-cpp-api)
target_link_libraries(etcd-cpp-api PUBLIC target_link_libraries(etcd-cpp-api PUBLIC
${Boost_LIBRARIES}
${CPPREST_LIB} # n.b.: the asynchronous client requires pplx in cpprestsdk ${CPPREST_LIB} # n.b.: the asynchronous client requires pplx in cpprestsdk
${PROTOBUF_LIBRARIES}
${OPENSSL_LIBRARIES} ${OPENSSL_LIBRARIES}
${GRPC_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) 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-core etcd-cpp-api install(TARGETS etcd-cpp-api-core etcd-cpp-api
@ -56,4 +84,3 @@ else()
install(TARGETS etcd-cpp-api-core etcd-cpp-api install(TARGETS etcd-cpp-api-core etcd-cpp-api
EXPORT etcd-targets) EXPORT etcd-targets)
endif() endif()

View File

@ -1,5 +1,6 @@
#if defined(_WIN32) #if defined(_WIN32)
// see also: https://stackoverflow.com/questions/2561368/illegal-token-on-right-side-of // see also:
// https://stackoverflow.com/questions/2561368/illegal-token-on-right-side-of
#define NOMINMAX #define NOMINMAX
#endif #endif
@ -19,8 +20,8 @@
#endif #endif
#include <chrono> #include <chrono>
#include <iostream>
#include <fstream> #include <fstream>
#include <iostream>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -28,62 +29,36 @@
#include <thread> #include <thread>
#include <utility> #include <utility>
#include <boost/algorithm/string.hpp>
#include <grpc++/grpc++.h> #include <grpc++/grpc++.h>
#include <grpc++/security/credentials.h> #include <grpc++/security/credentials.h>
#include "proto/rpc.grpc.pb.h" #include "proto/rpc.grpc.pb.h"
#include "proto/v3lock.grpc.pb.h"
#include "proto/v3election.grpc.pb.h" #include "proto/v3election.grpc.pb.h"
#include "proto/v3lock.grpc.pb.h"
#include "etcd/Client.hpp" #include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp" #include "etcd/KeepAlive.hpp"
#include "etcd/Watcher.hpp" #include "etcd/Watcher.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Action.hpp" #include "etcd/v3/Action.hpp"
#include "etcd/v3/AsyncRangeResponse.hpp" #include "etcd/v3/AsyncGRPC.hpp"
#include "etcd/v3/AsyncWatchResponse.hpp"
#include "etcd/v3/AsyncDeleteResponse.hpp"
#include "etcd/v3/AsyncPutResponse.hpp"
#include "etcd/v3/AsyncLockResponse.hpp"
#include "etcd/v3/AsyncElectionResponse.hpp"
#include "etcd/v3/AsyncTxnResponse.hpp"
#include "etcd/v3/Transaction.hpp" #include "etcd/v3/Transaction.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/AsyncSetAction.hpp" etcd::Client::Client(etcd::SyncClient* client) : client(client) {
#include "etcd/v3/AsyncCompareAndSwapAction.hpp"
#include "etcd/v3/AsyncCompareAndDeleteAction.hpp"
#include "etcd/v3/AsyncUpdateAction.hpp"
#include "etcd/v3/AsyncHeadAction.hpp"
#include "etcd/v3/AsyncRangeAction.hpp"
#include "etcd/v3/AsyncDeleteAction.hpp"
#include "etcd/v3/AsyncPutAction.hpp"
#include "etcd/v3/AsyncWatchAction.hpp"
#include "etcd/v3/AsyncLeaseAction.hpp"
#include "etcd/v3/AsyncLockAction.hpp"
#include "etcd/v3/AsyncElectionAction.hpp"
#include "etcd/v3/AsyncTxnAction.hpp"
etcd::Client::Client(etcd::SyncClient *client): client(client)
{
this->own_client = false; this->own_client = false;
} }
etcd::Client* WithClient(etcd::SyncClient *client) etcd::Client* WithClient(etcd::SyncClient* client) {
{
return new etcd::Client(client); return new etcd::Client(client);
} }
etcd::Client::Client(std::string const& address, etcd::Client::Client(std::string const& address,
std::string const & load_balancer) std::string const& load_balancer) {
{
this->own_client = true; this->own_client = true;
this->client = new SyncClient(address, load_balancer); this->client = new SyncClient(address, load_balancer);
} }
etcd::Client::Client(std::string const& address, etcd::Client::Client(std::string const& address,
grpc::ChannelArguments const & arguments) grpc::ChannelArguments const& arguments) {
{
this->own_client = true; this->own_client = true;
this->client = new SyncClient(address, arguments); this->client = new SyncClient(address, arguments);
} }
@ -98,24 +73,20 @@ etcd::Client *etcd::Client::WithUrl(std::string const & etcd_url,
return new etcd::Client(etcd_url, arguments); return new etcd::Client(etcd_url, arguments);
} }
etcd::Client::Client(std::string const & address, etcd::Client::Client(std::string const& address, std::string const& username,
std::string const & username, std::string const& password, int const auth_token_ttl,
std::string const & password, std::string const& load_balancer) {
int const auth_token_ttl,
std::string const & load_balancer)
{
this->own_client = true; this->own_client = true;
this->client = new SyncClient(address, username, password, auth_token_ttl, load_balancer); this->client = new SyncClient(address, username, password, auth_token_ttl,
load_balancer);
} }
etcd::Client::Client(std::string const & address, etcd::Client::Client(std::string const& address, std::string const& username,
std::string const & username, std::string const& password, int const auth_token_ttl,
std::string const & password, grpc::ChannelArguments const& arguments) {
int const auth_token_ttl,
grpc::ChannelArguments const & arguments)
{
this->own_client = true; this->own_client = true;
this->client = new SyncClient(address, username, password, auth_token_ttl, arguments); this->client =
new SyncClient(address, username, password, auth_token_ttl, arguments);
} }
etcd::Client* etcd::Client::WithUser(std::string const& etcd_url, etcd::Client* etcd::Client::WithUser(std::string const& etcd_url,
@ -123,7 +94,8 @@ etcd::Client *etcd::Client::WithUser(std::string const & etcd_url,
std::string const& password, std::string const& password,
int const auth_token_ttl, int const auth_token_ttl,
std::string const& load_balancer) { std::string const& load_balancer) {
return new etcd::Client(etcd_url, username, password, auth_token_ttl, load_balancer); return new etcd::Client(etcd_url, username, password, auth_token_ttl,
load_balancer);
} }
etcd::Client* etcd::Client::WithUser(std::string const& etcd_url, etcd::Client* etcd::Client::WithUser(std::string const& etcd_url,
@ -131,30 +103,26 @@ etcd::Client *etcd::Client::WithUser(std::string const & etcd_url,
std::string const& password, std::string const& password,
int const auth_token_ttl, int const auth_token_ttl,
grpc::ChannelArguments const& arguments) { grpc::ChannelArguments const& arguments) {
return new etcd::Client(etcd_url, username, password, auth_token_ttl, arguments); return new etcd::Client(etcd_url, username, password, auth_token_ttl,
arguments);
} }
etcd::Client::Client(std::string const& address, std::string const& ca,
etcd::Client::Client(std::string const & address, std::string const& cert, std::string const& privkey,
std::string const & ca,
std::string const & cert,
std::string const & privkey,
std::string const& target_name_override, std::string const& target_name_override,
std::string const & load_balancer) std::string const& load_balancer) {
{
this->own_client = true; this->own_client = true;
this->client = new SyncClient(address, ca, cert, privkey, target_name_override, load_balancer); this->client = new SyncClient(address, ca, cert, privkey,
target_name_override, load_balancer);
} }
etcd::Client::Client(std::string const & address, etcd::Client::Client(std::string const& address, std::string const& ca,
std::string const & ca, std::string const& cert, std::string const& privkey,
std::string const & cert,
std::string const & privkey,
std::string const& target_name_override, std::string const& target_name_override,
grpc::ChannelArguments const & arguments) grpc::ChannelArguments const& arguments) {
{
this->own_client = true; this->own_client = true;
this->client = new SyncClient(address, ca, cert, privkey, target_name_override, arguments); this->client = new SyncClient(address, ca, cert, privkey,
target_name_override, arguments);
} }
etcd::Client* etcd::Client::WithSSL(std::string const& etcd_url, etcd::Client* etcd::Client::WithSSL(std::string const& etcd_url,
@ -163,7 +131,8 @@ etcd::Client *etcd::Client::WithSSL(std::string const & etcd_url,
std::string const& privkey, std::string const& privkey,
std::string const& target_name_override, std::string const& target_name_override,
std::string const& load_balancer) { std::string const& load_balancer) {
return new etcd::Client(etcd_url, ca, cert, privkey, target_name_override, load_balancer); return new etcd::Client(etcd_url, ca, cert, privkey, target_name_override,
load_balancer);
} }
etcd::Client* etcd::Client::WithSSL(std::string const& etcd_url, etcd::Client* etcd::Client::WithSSL(std::string const& etcd_url,
@ -172,7 +141,8 @@ etcd::Client *etcd::Client::WithSSL(std::string const & etcd_url,
std::string const& cert, std::string const& cert,
std::string const& privkey, std::string const& privkey,
std::string const& target_name_override) { std::string const& target_name_override) {
return new etcd::Client(etcd_url, ca, cert, privkey, target_name_override, arguments); return new etcd::Client(etcd_url, ca, cert, privkey, target_name_override,
arguments);
} }
etcd::Client::~Client() { etcd::Client::~Client() {
@ -181,7 +151,6 @@ etcd::Client::~Client() {
} }
} }
namespace etcd { namespace etcd {
namespace detail { namespace detail {
@ -190,37 +159,46 @@ namespace detail {
// Inspired by: https://stackoverflow.com/a/20669290/5080177 // Inspired by: https://stackoverflow.com/a/20669290/5080177
template <typename A, typename F> template <typename A, typename F>
class capture_impl class capture_impl {
{
mutable A a; mutable A a;
mutable F f; mutable F f;
public:
capture_impl(A && a, F && f)
: a{std::forward<A>(a)}, f{std::forward<F>(f)}
{}
template <typename ...Ts> auto operator()( Ts&&...args ) public:
-> decltype(std::forward<F>(f)(std::forward<A>(a), std::forward<Ts>(args)... )) capture_impl(A&& a, F&& f) : a{std::forward<A>(a)}, f{std::forward<F>(f)} {}
{
template <typename... Ts>
auto operator()(Ts&&... args)
-> decltype(std::forward<F>(f)(std::forward<A>(a),
std::forward<Ts>(args)...)) {
return std::forward<F>(f)(std::forward<A>(a), std::forward<Ts>(args)...); return std::forward<F>(f)(std::forward<A>(a), std::forward<Ts>(args)...);
} }
template <typename ...Ts> auto operator()( Ts&&...args ) const template <typename... Ts>
-> decltype(std::forward<F>(f)(std::forward<A>(a), std::forward<Ts>(args)... )) auto operator()(Ts&&... args) const
{ -> decltype(std::forward<F>(f)(std::forward<A>(a),
std::forward<Ts>(args)...)) {
return std::forward<F>(f)(std::forward<A>(a), std::forward<Ts>(args)...); return std::forward<F>(f)(std::forward<A>(a), std::forward<Ts>(args)...);
} }
}; };
template <typename A, typename F> template <typename A, typename F>
static capture_impl<A, F> capture(A && a, F && f) static capture_impl<A, F> capture(A&& a, F&& f) {
{
return capture_impl<A, F>(std::forward<A>(a), std::forward<F>(f)); return capture_impl<A, F>(std::forward<A>(a), std::forward<F>(f));
} }
template <typename F, typename Params, typename... Ts> template <typename F, typename Params, typename... Ts>
static auto asyncify(F && fn, Params && params, Ts... args) -> pplx::task<decltype(capture(std::forward<Params>(params), std::bind(std::forward<F>(fn), std::placeholders::_1, std::forward<Ts>(args)...))())> { static auto asyncify(F&& fn, Params&& params, Ts... args)
return pplx::task<decltype(capture(std::forward<Params>(params), std::bind(std::forward<F>(fn), std::placeholders::_1, std::forward<Ts>(args)...))())>(capture(std::forward<Params>(params), std::bind(std::forward<F>(fn), std::placeholders::_1, std::forward<Ts>(args)...))); -> pplx::task<decltype(capture(std::forward<Params>(params),
std::bind(std::forward<F>(fn),
std::placeholders::_1,
std::forward<Ts>(args)...))())> {
return pplx::task<decltype(
capture(std::forward<Params>(params),
std::bind(std::forward<F>(fn), std::placeholders::_1,
std::forward<Ts>(args)...))())>(
capture(std::forward<Params>(params),
std::bind(std::forward<F>(fn), std::placeholders::_1,
std::forward<Ts>(args)...)));
} }
} // namespace detail } // namespace detail
@ -230,334 +208,323 @@ using responser_t = etcd::Response (*)(std::shared_ptr<T>);
} // namespace etcd } // namespace etcd
pplx::task<etcd::Response> etcd::Client::head() pplx::task<etcd::Response> etcd::Client::head() {
{
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<etcd::responser_t<etcdv3::AsyncHeadAction>>(Response::create), static_cast<etcd::responser_t<etcdv3::AsyncHeadAction>>(Response::create),
this->client->head_internal()); this->client->head_internal());
} }
pplx::task<etcd::Response> etcd::Client::get(std::string const & key) pplx::task<etcd::Response> etcd::Client::get(std::string const& key) {
{
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<etcd::responser_t<etcdv3::AsyncRangeAction>>(Response::create), static_cast<etcd::responser_t<etcdv3::AsyncRangeAction>>(
Response::create),
this->client->get_internal(key)); this->client->get_internal(key));
} }
pplx::task<etcd::Response> etcd::Client::set(std::string const & key, std::string const & value, int ttl) pplx::task<etcd::Response> etcd::Client::get(std::string const& key,
{ int64_t revision) {
if (ttl > 0) {
return this->leasegrant(ttl).then([this, key, value](pplx::task<etcd::Response> const &task) {
auto resp = task.get();
if (resp.error_code() == 0) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncSetAction>>(Response::create), static_cast<etcd::responser_t<etcdv3::AsyncRangeAction>>(
this->client->set_internal(key, value, resp.value().lease())); Response::create),
} else { this->client->get_internal(key, revision));
return pplx::task_from_result(resp);
}
});
} else {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncSetAction>>(Response::create),
this->client->set_internal(key, value, 0));
}
} }
pplx::task<etcd::Response> etcd::Client::set(std::string const & key, std::string const & value, int64_t leaseid) pplx::task<etcd::Response> etcd::Client::set(std::string const& key,
{ std::string const& value,
const int64_t leaseid) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncSetAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncPutAction>>(Response::create),
this->client->set_internal(key, value, leaseid)); this->client->put_internal(key, value, leaseid));
} }
pplx::task<etcd::Response> etcd::Client::add(std::string const & key, std::string const & value, int ttl) pplx::task<etcd::Response> etcd::Client::add(std::string const& key,
{ std::string const& value,
if (ttl > 0) { const int64_t leaseid) {
return this->leasegrant(ttl).then([this, key, value](pplx::task<etcd::Response> const &task) {
auto resp = task.get();
if (resp.error_code() == 0) {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncSetAction>>(Response::create),
this->client->add_internal(key, value, resp.value().lease()));
} else {
return pplx::task_from_result(resp);
}
});
} else {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncSetAction>>(Response::create),
this->client->add_internal(key, value, 0));
}
}
pplx::task<etcd::Response> etcd::Client::add(std::string const & key, std::string const & value, int64_t leaseid)
{
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncSetAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncSetAction>>(Response::create),
this->client->add_internal(key, value, leaseid)); this->client->add_internal(key, value, leaseid));
} }
pplx::task<etcd::Response> etcd::Client::put(std::string const & key, std::string const & value) { pplx::task<etcd::Response> etcd::Client::put(std::string const& key,
std::string const& value) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncPutAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncPutAction>>(Response::create),
this->client->put_internal(key, value)); this->client->put_internal(key, value));
} }
pplx::task<etcd::Response> etcd::Client::modify(std::string const & key, std::string const & value, int ttl) pplx::task<etcd::Response> etcd::Client::put(std::string const& key,
{ std::string const& value,
if (ttl > 0) { const int64_t leaseId) {
return this->leasegrant(ttl).then([this, key, value](pplx::task<etcd::Response> const &task) {
auto resp = task.get();
if (resp.error_code() == 0) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncUpdateAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncPutAction>>(Response::create),
this->client->modify_internal(key, value, resp.value().lease())); this->client->put_internal(key, value, leaseId));
} else {
return pplx::task_from_result(resp);
}
});
} else {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncUpdateAction>>(Response::create),
this->client->modify_internal(key, value, 0));
}
} }
pplx::task<etcd::Response> etcd::Client::modify(std::string const & key, std::string const & value, int64_t leaseid) pplx::task<etcd::Response> etcd::Client::modify(std::string const& key,
{ std::string const& value,
const int64_t leaseid) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncUpdateAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncUpdateAction>>(Response::create),
this->client->modify_internal(key, value, leaseid)); this->client->modify_internal(key, value, leaseid));
} }
pplx::task<etcd::Response> etcd::Client::modify_if(std::string const & key, std::string const & value, std::string const & old_value, int ttl) pplx::task<etcd::Response> etcd::Client::modify_if(std::string const& key,
{ std::string const& value,
if (ttl > 0) { std::string const& old_value,
return this->leasegrant(ttl).then([this, key, value, old_value](pplx::task<etcd::Response> const &task) { const int64_t leaseid) {
auto resp = task.get();
if (resp.error_code() == 0) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncCompareAndSwapAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncCompareAndSwapAction>>(
this->client->modify_if_internal(key, value, 0, old_value, resp.value().lease(), etcdv3::AtomicityType::PREV_VALUE)); Response::create),
} else { this->client->modify_if_internal(key, value, 0, old_value,
return pplx::task_from_result(resp); etcdv3::AtomicityType::PREV_VALUE,
} leaseid));
});
} else {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncCompareAndSwapAction>>(Response::create),
this->client->modify_if_internal(key, value, 0, old_value, 0, etcdv3::AtomicityType::PREV_VALUE));
}
} }
pplx::task<etcd::Response> etcd::Client::modify_if(std::string const & key, std::string const & value, std::string const & old_value, int64_t leaseid) pplx::task<etcd::Response> etcd::Client::modify_if(std::string const& key,
{ std::string const& value,
int64_t old_index,
const int64_t leaseid) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncCompareAndSwapAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncCompareAndSwapAction>>(
this->client->modify_if_internal(key, value, 0, old_value, leaseid, etcdv3::AtomicityType::PREV_VALUE)); Response::create),
this->client->modify_if_internal(key, value, old_index, "",
etcdv3::AtomicityType::PREV_INDEX,
leaseid));
} }
pplx::task<etcd::Response> etcd::Client::modify_if(std::string const & key, std::string const & value, int64_t old_index, int ttl) pplx::task<etcd::Response> etcd::Client::rm(std::string const& key) {
{
if (ttl > 0) {
return this->leasegrant(ttl).then([this, key, value, old_index](pplx::task<etcd::Response> const &task) {
auto resp = task.get();
if (resp.error_code() == 0) {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncCompareAndSwapAction>>(Response::create),
this->client->modify_if_internal(key, value, old_index, "", resp.value().lease(), etcdv3::AtomicityType::PREV_INDEX));
} else {
return pplx::task_from_result(resp);
}
});
} else {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncCompareAndSwapAction>>(Response::create),
this->client->modify_if_internal(key, value, old_index, "", 0, etcdv3::AtomicityType::PREV_INDEX));
}
}
pplx::task<etcd::Response> etcd::Client::modify_if(std::string const & key, std::string const & value, int64_t old_index, int64_t leaseid)
{
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncCompareAndSwapAction>>(Response::create),
this->client->modify_if_internal(key, value, old_index, "", leaseid, etcdv3::AtomicityType::PREV_INDEX));
}
pplx::task<etcd::Response> etcd::Client::rm(std::string const & key)
{
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncDeleteAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncDeleteAction>>(Response::create),
this->client->rm_internal(key)); this->client->rm_internal(key));
} }
pplx::task<etcd::Response> etcd::Client::rm_if(std::string const & key, std::string const & old_value) pplx::task<etcd::Response> etcd::Client::rm_if(std::string const& key,
{ std::string const& old_value) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncCompareAndDeleteAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncCompareAndDeleteAction>>(
this->client->rm_if_internal(key, 0, old_value, etcdv3::AtomicityType::PREV_VALUE)); Response::create),
this->client->rm_if_internal(key, 0, old_value,
etcdv3::AtomicityType::PREV_VALUE));
} }
pplx::task<etcd::Response> etcd::Client::rm_if(std::string const & key, int64_t old_index) pplx::task<etcd::Response> etcd::Client::rm_if(std::string const& key,
{ int64_t old_index) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncCompareAndDeleteAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncCompareAndDeleteAction>>(
this->client->rm_if_internal(key, old_index, "", etcdv3::AtomicityType::PREV_INDEX)); Response::create),
this->client->rm_if_internal(key, old_index, "",
etcdv3::AtomicityType::PREV_INDEX));
} }
pplx::task<etcd::Response> etcd::Client::rmdir(std::string const & key, bool recursive) pplx::task<etcd::Response> etcd::Client::rmdir(std::string const& key,
{ bool recursive) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncDeleteAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncDeleteAction>>(Response::create),
this->client->rmdir_internal(key, recursive)); this->client->rmdir_internal(key, recursive));
} }
pplx::task<etcd::Response> etcd::Client::rmdir(std::string const & key, const char *range_end) pplx::task<etcd::Response> etcd::Client::rmdir(std::string const& key,
{ const char* range_end) {
return this->rmdir(key, std::string(range_end)); return this->rmdir(key, std::string(range_end));
} }
pplx::task<etcd::Response> etcd::Client::rmdir(std::string const & key, std::string const &range_end) pplx::task<etcd::Response> etcd::Client::rmdir(std::string const& key,
{ std::string const& range_end) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncDeleteAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncDeleteAction>>(Response::create),
this->client->rmdir_internal(key, range_end)); this->client->rmdir_internal(key, range_end));
} }
pplx::task<etcd::Response> etcd::Client::ls(std::string const & key) pplx::task<etcd::Response> etcd::Client::ls(std::string const& key) {
{
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, 0)); this->client->ls_internal(key, 0));
} }
pplx::task<etcd::Response> etcd::Client::ls(std::string const & key, size_t const limit) pplx::task<etcd::Response> etcd::Client::ls(std::string const& key,
{ size_t const limit) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, limit)); this->client->ls_internal(key, limit));
} }
pplx::task<etcd::Response> etcd::Client::ls(std::string const & key, std::string const &range_end) pplx::task<etcd::Response> etcd::Client::ls(std::string const& key,
{ size_t const limit,
int64_t revision) {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, limit, false, revision));
}
pplx::task<etcd::Response> etcd::Client::ls(std::string const& key,
std::string const& range_end) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, range_end, 0)); this->client->ls_internal(key, range_end, 0));
} }
pplx::task<etcd::Response> etcd::Client::ls(std::string const & key, std::string const &range_end, size_t const limit) pplx::task<etcd::Response> etcd::Client::ls(std::string const& key,
{ std::string const& range_end,
size_t const limit) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, range_end, limit)); this->client->ls_internal(key, range_end, limit));
} }
pplx::task<etcd::Response> etcd::Client::keys(std::string const & key) pplx::task<etcd::Response> etcd::Client::ls(std::string const& key,
{ std::string const& range_end,
size_t const limit,
int64_t revision) {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, range_end, limit, false, revision));
}
pplx::task<etcd::Response> etcd::Client::keys(std::string const& key) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, 0, true)); this->client->ls_internal(key, 0, true));
} }
pplx::task<etcd::Response> etcd::Client::keys(std::string const & key, size_t const limit) pplx::task<etcd::Response> etcd::Client::keys(std::string const& key,
{ size_t const limit) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, limit, true)); this->client->ls_internal(key, limit, true));
} }
pplx::task<etcd::Response> etcd::Client::keys(std::string const & key, std::string const &range_end) pplx::task<etcd::Response> etcd::Client::keys(std::string const& key,
{ size_t const limit,
int64_t revision) {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, limit, true, revision));
}
pplx::task<etcd::Response> etcd::Client::keys(std::string const& key,
std::string const& range_end) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, range_end, 0, true)); this->client->ls_internal(key, range_end, 0, true));
} }
pplx::task<etcd::Response> etcd::Client::keys(std::string const & key, std::string const &range_end, size_t const limit) pplx::task<etcd::Response> etcd::Client::keys(std::string const& key,
{ std::string const& range_end,
size_t const limit) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, range_end, limit, true)); this->client->ls_internal(key, range_end, limit, true));
} }
pplx::task<etcd::Response> etcd::Client::watch(std::string const & key, bool recursive) pplx::task<etcd::Response> etcd::Client::keys(std::string const& key,
{ std::string const& range_end,
size_t const limit,
int64_t revision) {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRangeAction>>(Response::create),
this->client->ls_internal(key, range_end, limit, true, revision));
}
pplx::task<etcd::Response> etcd::Client::watch(std::string const& key,
bool recursive) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncWatchAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncWatchAction>>(Response::create),
this->client->watch_internal(key, 0, recursive)); this->client->watch_internal(key, 0, recursive));
} }
pplx::task<etcd::Response> etcd::Client::watch(std::string const & key, int64_t fromIndex, bool recursive) pplx::task<etcd::Response> etcd::Client::watch(std::string const& key,
{ int64_t fromIndex,
bool recursive) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncWatchAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncWatchAction>>(Response::create),
this->client->watch_internal(key, fromIndex, recursive)); this->client->watch_internal(key, fromIndex, recursive));
} }
pplx::task<etcd::Response> etcd::Client::watch(std::string const & key, const char *range_end) pplx::task<etcd::Response> etcd::Client::watch(std::string const& key,
{ const char* range_end) {
return this->watch(key, std::string(range_end)); return this->watch(key, std::string(range_end));
} }
pplx::task<etcd::Response> etcd::Client::watch(std::string const & key, std::string const & range_end) pplx::task<etcd::Response> etcd::Client::watch(std::string const& key,
{ std::string const& range_end) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncWatchAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncWatchAction>>(Response::create),
this->client->watch_internal(key, range_end, 0)); this->client->watch_internal(key, range_end, 0));
} }
pplx::task<etcd::Response> etcd::Client::watch(std::string const & key, std::string const & range_end, int64_t fromIndex) pplx::task<etcd::Response> etcd::Client::watch(std::string const& key,
{ std::string const& range_end,
int64_t fromIndex) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncWatchAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncWatchAction>>(Response::create),
this->client->watch_internal(key, range_end, fromIndex)); this->client->watch_internal(key, range_end, fromIndex));
} }
pplx::task<etcd::Response> etcd::Client::leasegrant(int ttl) pplx::task<etcd::Response> etcd::Client::leasegrant(int ttl) {
{
// See Note [lease with TTL and issue the actual request] // See Note [lease with TTL and issue the actual request]
return pplx::task<etcd::Response>([this, ttl]() { return pplx::task<etcd::Response>(
return this->client->leasegrant(ttl); [this, ttl]() { return this->client->leasegrant(ttl); });
});
} }
pplx::task<std::shared_ptr<etcd::KeepAlive>> etcd::Client::leasekeepalive(int ttl) { pplx::task<std::shared_ptr<etcd::KeepAlive>> etcd::Client::leasekeepalive(
int ttl) {
// See Note [lease with TTL and issue the actual request] // See Note [lease with TTL and issue the actual request]
return pplx::task<std::shared_ptr<etcd::KeepAlive>>([this, ttl]() { return pplx::task<std::shared_ptr<etcd::KeepAlive>>(
return this->client->leasekeepalive(ttl); [this, ttl]() { return this->client->leasekeepalive(ttl); });
});
} }
pplx::task<etcd::Response> etcd::Client::leaserevoke(int64_t lease_id) pplx::task<etcd::Response> etcd::Client::leaserevoke(int64_t lease_id) {
{
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncLeaseRevokeAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncLeaseRevokeAction>>(
Response::create),
this->client->leaserevoke_internal(lease_id)); this->client->leaserevoke_internal(lease_id));
} }
pplx::task<etcd::Response> etcd::Client::leasetimetolive(int64_t lease_id) pplx::task<etcd::Response> etcd::Client::leasetimetolive(int64_t lease_id) {
{
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncLeaseTimeToLiveAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncLeaseTimeToLiveAction>>(
Response::create),
this->client->leasetimetolive_internal(lease_id)); this->client->leasetimetolive_internal(lease_id));
} }
pplx::task<etcd::Response> etcd::Client::leases() pplx::task<etcd::Response> etcd::Client::leases() {
{
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncLeaseLeasesAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncLeaseLeasesAction>>(
Response::create),
this->client->leases_internal()); this->client->leases_internal());
} }
pplx::task<etcd::Response> etcd::Client::add_member(
std::string const& peer_urls, bool is_learner) {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncAddMemberAction>>(Response::create),
this->client->add_member_internal(peer_urls, is_learner));
}
pplx::task<etcd::Response> etcd::Client::list_member() {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncListMemberAction>>(Response::create),
this->client->list_member_internal());
}
pplx::task<etcd::Response> etcd::Client::remove_member(
const uint64_t member_id) {
return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncRemoveMemberAction>>(
Response::create),
this->client->remove_member_internal(member_id));
}
pplx::task<etcd::Response> etcd::Client::lock(std::string const& key) { pplx::task<etcd::Response> etcd::Client::lock(std::string const& key) {
static const int DEFAULT_LEASE_TTL_FOR_LOCK = 10; // see also etcd::SyncClient::lock static const int DEFAULT_LEASE_TTL_FOR_LOCK =
10; // see also etcd::SyncClient::lock
return this->lock(key, DEFAULT_LEASE_TTL_FOR_LOCK); return this->lock(key, DEFAULT_LEASE_TTL_FOR_LOCK);
} }
pplx::task<etcd::Response> etcd::Client::lock(std::string const& key, pplx::task<etcd::Response> etcd::Client::lock(std::string const& key,
const bool sync) { const bool sync) {
static const int DEFAULT_LEASE_TTL_FOR_LOCK = 10; // see also etcd::SyncClient::lock static const int DEFAULT_LEASE_TTL_FOR_LOCK =
10; // see also etcd::SyncClient::lock
return this->lock(key, DEFAULT_LEASE_TTL_FOR_LOCK, sync); return this->lock(key, DEFAULT_LEASE_TTL_FOR_LOCK, sync);
} }
@ -567,16 +534,14 @@ pplx::task<etcd::Response> etcd::Client::lock(std::string const &key,
// See also SyncClient::lock // See also SyncClient::lock
// //
// We don't separate the keepalive step and lock step to avoid leaving many // We don't separate the keepalive step and lock step to avoid leaving many
// busy-waiting keepalive tasks when waiting for the locks due to the contention // busy-waiting keepalive tasks when waiting for the locks due to the
// of async threadpool. // contention of async threadpool.
return pplx::task<etcd::Response>([this, key, lease_ttl]() { return pplx::task<etcd::Response>(
return this->client->lock(key, lease_ttl); [this, key, lease_ttl]() { return this->client->lock(key, lease_ttl); });
});
} }
pplx::task<etcd::Response> etcd::Client::lock(std::string const& key, pplx::task<etcd::Response> etcd::Client::lock(std::string const& key,
int lease_ttl, int lease_ttl, const bool sync) {
const bool sync) {
if (sync) { if (sync) {
pplx::task_completion_event<etcd::Response> event; pplx::task_completion_event<etcd::Response> event;
event.set(this->client->lock(key, lease_ttl)); event.set(this->client->lock(key, lease_ttl));
@ -617,16 +582,19 @@ pplx::task<etcd::Response> etcd::Client::txn(etcdv3::Transaction const &txn) {
this->client->txn_internal(txn)); this->client->txn_internal(txn));
} }
pplx::task<etcd::Response> etcd::Client::campaign( pplx::task<etcd::Response> etcd::Client::campaign(std::string const& name,
std::string const &name, int64_t lease_id, std::string const &value) { int64_t lease_id,
std::string const& value) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncCampaignAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncCampaignAction>>(Response::create),
this->client->campaign_internal(name, lease_id, value)); this->client->campaign_internal(name, lease_id, value));
} }
pplx::task<etcd::Response> etcd::Client::proclaim( pplx::task<etcd::Response> etcd::Client::proclaim(std::string const& name,
std::string const &name, int64_t lease_id, int64_t lease_id,
std::string const &key, int64_t revision, std::string const &value) { std::string const& key,
int64_t revision,
std::string const& value) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncProclaimAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncProclaimAction>>(Response::create),
this->client->proclaim_internal(name, lease_id, key, revision, value)); this->client->proclaim_internal(name, lease_id, key, revision, value));
@ -638,12 +606,15 @@ pplx::task<etcd::Response> etcd::Client::leader(std::string const &name) {
this->client->leader_internal(name)); this->client->leader_internal(name));
} }
std::unique_ptr<etcd::Client::Observer> etcd::Client::observe(std::string const &name) { std::unique_ptr<etcd::Client::Observer> etcd::Client::observe(
std::string const& name) {
return this->client->observe(name); return this->client->observe(name);
} }
pplx::task<etcd::Response> etcd::Client::resign( pplx::task<etcd::Response> etcd::Client::resign(std::string const& name,
std::string const &name, int64_t lease_id, std::string const &key, int64_t revision) { int64_t lease_id,
std::string const& key,
int64_t revision) {
return etcd::detail::asyncify( return etcd::detail::asyncify(
static_cast<responser_t<etcdv3::AsyncResignAction>>(Response::create), static_cast<responser_t<etcdv3::AsyncResignAction>>(Response::create),
this->client->resign_internal(name, lease_id, key, revision)); this->client->resign_internal(name, lease_id, key, revision));
@ -657,72 +628,61 @@ std::shared_ptr<grpc::Channel> etcd::Client::grpc_channel() const {
return this->client->grpc_channel(); return this->client->grpc_channel();
} }
etcd::SyncClient* etcd::Client::sync_client() const { etcd::SyncClient* etcd::Client::sync_client() const { return this->client; }
return this->client;
}
// watchers from the async client // watchers from the async client
etcd::Watcher::Watcher(Client const& client, std::string const& key, etcd::Watcher::Watcher(Client const& client, std::string const& key,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive)
bool recursive): : Watcher(*client.sync_client(), key, callback, wait_callback, recursive) {}
Watcher(*client.sync_client(), key, callback, wait_callback, recursive) {
}
etcd::Watcher::Watcher(Client const& client, std::string const& key, etcd::Watcher::Watcher(Client 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,
std::function<void(bool)> wait_callback): std::function<void(bool)> wait_callback)
Watcher(*client.sync_client(), key, range_end, callback, wait_callback) { : Watcher(*client.sync_client(), key, range_end, callback, wait_callback) {}
}
etcd::Watcher::Watcher(Client const &client, std::string const & key, int64_t fromIndex, etcd::Watcher::Watcher(Client const& client, std::string const& key,
int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive)
bool recursive): : Watcher(*client.sync_client(), key, fromIndex, callback, wait_callback,
Watcher(*client.sync_client(), key, fromIndex, callback, wait_callback, recursive) { recursive) {}
}
etcd::Watcher::Watcher(Client const& client, std::string const& key, etcd::Watcher::Watcher(Client const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback): std::function<void(bool)> wait_callback)
Watcher(*client.sync_client(), key, range_end, fromIndex, callback, wait_callback) { : Watcher(*client.sync_client(), key, range_end, fromIndex, callback,
} wait_callback) {}
etcd::Watcher::Watcher(Client const& client, std::string const& key, etcd::Watcher::Watcher(Client const& client, std::string const& key,
std::function<void(Response)> callback, std::function<void(Response)> callback, bool recursive)
bool recursive): : Watcher(*client.sync_client(), key, callback, recursive) {}
Watcher(*client.sync_client(), key, callback, recursive) {
}
etcd::Watcher::Watcher(Client const& client, std::string const& key, etcd::Watcher::Watcher(Client 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.sync_client(), key, range_end, callback) { : Watcher(*client.sync_client(), key, range_end, callback) {}
}
etcd::Watcher::Watcher(Client const &client, std::string const & key, int64_t fromIndex, etcd::Watcher::Watcher(Client const& client, std::string const& key,
std::function<void(Response)> callback, int64_t fromIndex,
bool recursive): std::function<void(Response)> callback, bool recursive)
Watcher(*client.sync_client(), key, fromIndex, callback, recursive) { : Watcher(*client.sync_client(), key, fromIndex, callback, recursive) {}
}
etcd::Watcher::Watcher(Client const& client, std::string const& key, etcd::Watcher::Watcher(Client const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback): std::function<void(Response)> callback)
Watcher(*client.sync_client(), key, range_end, fromIndex, callback) { : Watcher(*client.sync_client(), key, range_end, fromIndex, callback) {}
}
// keepalive from then async client // keepalive from then async client
etcd::KeepAlive::KeepAlive(Client const &client, int ttl, int64_t lease_id): etcd::KeepAlive::KeepAlive(Client const& client, int ttl, int64_t lease_id)
KeepAlive(*client.sync_client(), ttl, lease_id) { : KeepAlive(*client.sync_client(), ttl, lease_id) {}
}
etcd::KeepAlive::KeepAlive(Client const &client, etcd::KeepAlive::KeepAlive(
std::function<void (std::exception_ptr)> const &handler, Client const& client,
int ttl, int64_t lease_id): std::function<void(std::exception_ptr)> const& handler, int ttl,
KeepAlive(*client.sync_client(), handler, ttl, lease_id) { int64_t lease_id)
} : KeepAlive(*client.sync_client(), handler, ttl, lease_id) {}

View File

@ -1,8 +1,9 @@
#include <chrono> #include <chrono>
#include <iostream>
#include <ratio> #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"
@ -16,15 +17,22 @@ 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(SyncClient 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()) { 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.grpc_channel()); stubs->leaseServiceStub = Lease::NewStub(client.grpc_channel());
@ -37,43 +45,56 @@ etcd::KeepAlive::KeepAlive(SyncClient const &client, int ttl, int64_t lease_id):
continue_next.store(true); continue_next.store(true);
stubs->call.reset(new etcdv3::AsyncLeaseKeepAliveAction(std::move(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(SyncClient(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, int const auth_token_ttl): std::string const& password, int ttl,
KeepAlive(SyncClient(address, username, password, auth_token_ttl), 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(std::string const & address, etcd::KeepAlive::KeepAlive(
std::string const & ca, std::string const& address, std::string const& ca, std::string const& cert,
std::string const & cert,
std::string const& privkey, std::string const& privkey,
std::function<void (std::exception_ptr)> const &handler, std::function<void(std::exception_ptr)> const& handler, int ttl,
int ttl, int64_t lease_id, int64_t lease_id, std::string const& target_name_override)
std::string const & target_name_override): : KeepAlive(SyncClient(address, ca, cert, privkey, target_name_override),
KeepAlive(SyncClient(address, ca, cert, privkey, target_name_override), ttl, lease_id) { handler, ttl, lease_id) {}
}
etcd::KeepAlive::KeepAlive(SyncClient const &client, etcd::KeepAlive::KeepAlive(
std::function<void (std::exception_ptr)> const &handler, SyncClient const& client,
int ttl, int64_t lease_id): std::function<void(std::exception_ptr)> const& handler, int ttl,
handler_(handler), ttl(ttl), lease_id(lease_id), continue_next(true), int64_t lease_id)
: handler_(handler),
ttl(ttl),
lease_id(lease_id),
continue_next(true),
grpc_timeout(client.get_grpc_timeout()) { 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.grpc_channel()); stubs->leaseServiceStub = Lease::NewStub(client.grpc_channel());
@ -84,91 +105,154 @@ etcd::KeepAlive::KeepAlive(SyncClient const &client,
params.lease_stub = stubs->leaseServiceStub.get(); params.lease_stub = stubs->leaseServiceStub.get();
stubs->call.reset(new etcdv3::AsyncLeaseKeepAliveAction(std::move(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());
} else {
eptr_ = std::current_exception(); eptr_ = std::current_exception();
} }
this->Cancel(); #else
const std::string err = this->refresh();
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(SyncClient(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, const int auth_token_ttl): std::function<void(std::exception_ptr)> const& handler, int ttl,
KeepAlive(SyncClient(address, username, password, auth_token_ttl), 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();
// clean up
if (task_.joinable()) {
task_.join();
}
}
void etcd::KeepAlive::Cancel() void etcd::KeepAlive::Cancel() {
{
std::lock_guard<std::mutex> scope_lock(mutex_for_refresh_);
if (!continue_next.exchange(false)) { if (!continue_next.exchange(false)) {
return; return;
} }
// stop the thread
cv_for_refresh_.notify_all();
refresh_task_.join();
// send a cancel request
{
std::lock_guard<std::mutex> lock(mutex_for_refresh_);
stubs->call->CancelKeepAlive(); stubs->call->CancelKeepAlive();
if (keepalive_timer_) {
keepalive_timer_->cancel();
} }
context.stop();
} }
void etcd::KeepAlive::Check() { 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_);
} }
void etcd::KeepAlive::refresh() #ifndef _ETCD_NO_EXCEPTIONS
{ if (eptr_) {
std::lock_guard<std::mutex> scope_lock(mutex_for_refresh_); // rethrow in `Check()` to keep the consistent semantics
if (!continue_next.load()) { std::rethrow_exception(eptr_);
}
#endif
return; return;
} }
std::string etcd::KeepAlive::refresh() {
while (true) {
if (!continue_next.load()) {
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))); {
keepalive_timer_->async_wait([this](const boost::system::error_code& error) { std::unique_lock<std::mutex> lock(mutex_for_refresh_);
if (error) { if (cv_for_refresh_.wait_for(lock, std::chrono::seconds(keepalive_ttl)) ==
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()) { }
// 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; this->stubs->call->mutable_parameters().grpc_timeout = this->grpc_timeout;
auto resp = this->stubs->call->Refresh(); auto resp = this->stubs->call->Refresh();
if (!resp.is_ok()) { if (!resp.is_ok()) {
throw std::runtime_error("Failed to refresh lease: error code: " + std::to_string(resp.error_code()) + const std::string err = "Failed to refresh lease: error code: " +
", message: " + resp.error_message()); 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) { if (resp.value().ttl() == 0) {
throw std::out_of_range("Failed to refresh lease due to expiration: the new TTL is 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;
} }
// trigger the next round; return std::string{};
this->refresh();
}
}
});
} }

View File

@ -3,11 +3,7 @@
#include <iostream> #include <iostream>
etcd::Response::Response() etcd::Response::Response() : _error_code(0), _index(0) {}
: _error_code(0),
_index(0)
{
}
etcd::Response::Response(const etcd::Response& response) { etcd::Response::Response(const etcd::Response& response) {
this->_error_code = response._error_code; this->_error_code = response._error_code;
@ -29,30 +25,29 @@ etcd::Response::Response(const etcd::Response & response) {
this->_raft_term = response._raft_term; this->_raft_term = response._raft_term;
this->_leases = response._leases; this->_leases = response._leases;
this->_members = response._members;
} }
etcd::Response::Response(const etcdv3::V3Response& reply, std::chrono::microseconds const& duration) 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(); _compact_revision = reply.get_compact_revision();
_watch_id = reply.get_watch_id();
_lock_key = reply.get_lock_key(); _lock_key = reply.get_lock_key();
_name = reply.get_name(); _name = reply.get_name();
@ -70,99 +65,57 @@ etcd::Response::Response(const etcdv3::V3Response& reply, std::chrono::microseco
// lease list // lease list
this->_leases = reply.get_leases(); this->_leases = reply.get_leases();
// member list
this->_members = reply.get_members();
} }
etcd::Response::Response(int error_code, std::string const& error_message) etcd::Response::Response(int error_code, std::string const& error_message)
: _error_code(error_code), : _error_code(error_code), _error_message(error_message), _index(0) {}
_error_message(error_message),
_index(0)
{
}
etcd::Response::Response(int error_code, char const* error_message) etcd::Response::Response(int error_code, char const* error_message)
: _error_code(error_code), : _error_code(error_code), _error_message(error_message), _index(0) {}
_error_message(error_message),
_index(0)
{
}
int etcd::Response::error_code() const int etcd::Response::error_code() const { return _error_code; }
{
return _error_code;
}
std::string const & etcd::Response::error_message() const std::string const& etcd::Response::error_message() const {
{
return _error_message; return _error_message;
} }
bool etcd::Response::is_ok() const bool etcd::Response::is_ok() const { return error_code() == 0; }
{
return error_code() == 0;
}
bool etcd::Response::is_network_unavailable() const bool etcd::Response::is_network_unavailable() const {
{
return error_code() == ::grpc::StatusCode::UNAVAILABLE; return error_code() == ::grpc::StatusCode::UNAVAILABLE;
} }
bool etcd::Response::is_grpc_timeout() const bool etcd::Response::is_grpc_timeout() const {
{
return _error_code == grpc::StatusCode::DEADLINE_EXCEEDED; return _error_code == grpc::StatusCode::DEADLINE_EXCEEDED;
} }
std::string const & etcd::Response::action() const std::string const& etcd::Response::action() const { return _action; }
{
return _action;
}
int64_t etcd::Response::index() const int64_t etcd::Response::index() const { return _index; }
{
return _index;
}
etcd::Value const & etcd::Response::value() const etcd::Value const& etcd::Response::value() const { return _value; }
{
return _value;
}
etcd::Value const & etcd::Response::prev_value() const etcd::Value const& etcd::Response::prev_value() const { return _prev_value; }
{
return _prev_value;
}
etcd::Values const & etcd::Response::values() const etcd::Values const& etcd::Response::values() const { return _values; }
{
return _values;
}
etcd::Value const & etcd::Response::value(int index) const 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];
}
int64_t etcd::Response::compact_revision() const int64_t etcd::Response::compact_revision() const { return _compact_revision; }
{
return _compact_revision;
}
std::string const & etcd::Response::lock_key() const { int64_t etcd::Response::watch_id() const { return _watch_id; }
return _lock_key;
}
std::string const & etcd::Response::name() const { std::string const& etcd::Response::lock_key() const { return _lock_key; }
return _name;
} std::string const& etcd::Response::name() const { return _name; }
std::vector<etcd::Event> const& etcd::Response::events() const { std::vector<etcd::Event> const& etcd::Response::events() const {
return this->_events; return this->_events;
@ -172,18 +125,16 @@ std::chrono::microseconds const& etcd::Response::duration() const {
return this->_duration; return this->_duration;
} }
uint64_t etcd::Response::cluster_id() const { uint64_t etcd::Response::cluster_id() const { return this->_cluster_id; }
return this->_cluster_id;
}
uint64_t etcd::Response::member_id() const { uint64_t etcd::Response::member_id() const { return this->_member_id; }
return this->_member_id;
}
uint64_t etcd::Response::raft_term() const { uint64_t etcd::Response::raft_term() const { return this->_raft_term; }
return this->_raft_term;
}
std::vector<int64_t> const& etcd::Response::leases() const { std::vector<int64_t> const& etcd::Response::leases() const {
return this->_leases; 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,21 +1,13 @@
#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),
_version(0),
_ttl(0),
leaseId(0)
{
}
etcd::Value::Value(etcdv3::KeyValue const& kv) {
etcd::Value::Value(etcdv3::KeyValue const & kv)
{
dir = false; dir = false;
_key = kv.kvs.key(); _key = kv.kvs.key();
value = kv.kvs.value(); value = kv.kvs.value();
@ -26,8 +18,7 @@ etcd::Value::Value(etcdv3::KeyValue const & kv)
_ttl = kv.get_ttl(); _ttl = kv.get_ttl();
} }
etcd::Value::Value(mvccpb::KeyValue const & kv) etcd::Value::Value(mvccpb::KeyValue const& kv) {
{
dir = false; dir = false;
_key = kv.key(); _key = kv.key();
value = kv.value(); value = kv.value();
@ -38,44 +29,33 @@ etcd::Value::Value(mvccpb::KeyValue const & kv)
_ttl = -1; _ttl = -1;
} }
std::string const & etcd::Value::key() const std::string const& etcd::Value::key() const { return _key; }
{
return _key;
}
bool etcd::Value::is_dir() const bool etcd::Value::is_dir() const { return dir; }
{
return dir;
}
std::string const & etcd::Value::as_string() const std::string const& etcd::Value::as_string() const { return value; }
{
return value;
}
int64_t etcd::Value::created_index() const int64_t etcd::Value::created_index() const { return created; }
{
return created;
}
int64_t etcd::Value::modified_index() const int64_t etcd::Value::modified_index() const { return modified; }
{
return modified;
}
int64_t etcd::Value::version() const int64_t etcd::Value::version() const { return _version; }
{
return _version;
}
int etcd::Value::ttl() const int etcd::Value::ttl() const { return _ttl; }
{
return _ttl;
}
int64_t etcd::Value::lease() const int64_t etcd::Value::lease() const { return leaseId; }
{
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;
} }
etcd::Event::Event(mvccpb::Event const& event) { etcd::Event::Event(mvccpb::Event const& event) {
@ -100,18 +80,37 @@ enum etcd::Event::EventType etcd::Event::event_type() const {
return event_type_; return event_type_;
} }
bool etcd::Event::has_kv() const { bool etcd::Event::has_kv() const { return _has_kv; }
return _has_kv;
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;
} }
bool etcd::Event::has_prev_kv() const { std::ostream& etcd::operator<<(std::ostream& os, const etcd::Event& event) {
return _has_prev_kv; os << "Event type: " << event.event_type();
if (event.has_kv()) {
os << ", KV: " << event.kv();
} }
if (event.has_prev_kv()) {
const etcd::Value &etcd::Event::kv() const { os << ", Prev KV: " << event.prev_kv();
return _kv;
} }
return os;
const etcd::Value &etcd::Event::prev_kv() const {
return _prev_kv;
} }

View File

@ -2,38 +2,42 @@
#include "etcd/SyncClient.hpp" #include "etcd/SyncClient.hpp"
#include "etcd/v3/AsyncWatchAction.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(SyncClient const& client, std::string const& key, etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive)
bool recursive): : Watcher(client, key, -1, callback, wait_callback, recursive) {}
Watcher(client, key, -1, callback, wait_callback, recursive) {
}
etcd::Watcher::Watcher(SyncClient 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,
std::function<void(bool)> wait_callback): std::function<void(bool)> wait_callback)
Watcher(client, key, range_end, -1, callback, wait_callback) { : Watcher(client, key, range_end, -1, callback, wait_callback) {}
}
etcd::Watcher::Watcher(SyncClient const &client, std::string const & key, int64_t fromIndex, etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive)
bool recursive): : wait_callback(wait_callback), fromIndex(fromIndex), recursive(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.current_auth_token(), callback); doWatch(key, "", client.current_auth_token(), callback);
@ -42,8 +46,8 @@ etcd::Watcher::Watcher(SyncClient const &client, std::string const & key, int64_
etcd::Watcher::Watcher(SyncClient const& client, std::string const& key, etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback): std::function<void(bool)> wait_callback)
wait_callback(wait_callback), fromIndex(fromIndex), recursive(false) { : 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.current_auth_token(), callback); doWatch(key, range_end, client.current_auth_token(), callback);
@ -51,203 +55,172 @@ etcd::Watcher::Watcher(SyncClient const &client, std::string const & key,
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, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive)
bool recursive): : Watcher(address, key, -1, callback, wait_callback, 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,
std::function<void(bool)> wait_callback): std::function<void(bool)> wait_callback)
Watcher(address, key, range_end, -1, callback, wait_callback) { : Watcher(address, key, range_end, -1, callback, wait_callback) {}
}
etcd::Watcher::Watcher(std::string const & address, std::string const & key, int64_t fromIndex, etcd::Watcher::Watcher(std::string const& address, std::string const& key,
int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback, bool recursive)
bool recursive): : Watcher(SyncClient(address), key, fromIndex, callback, wait_callback,
Watcher(SyncClient(address), key, fromIndex, callback, wait_callback, recursive) { 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, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback): std::function<void(bool)> wait_callback)
Watcher(SyncClient(address), key, range_end, fromIndex, callback, 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(bool)> wait_callback, bool 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, 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(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback,
bool recursive, int const auth_token_ttl)
int const auth_token_ttl): : Watcher(address, username, password, key, range_end, -1, callback,
Watcher(address, username, password, key, -1, callback, wait_callback, recursive, auth_token_ttl) { 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,
int64_t fromIndex,
std::function<void(Response)> callback,
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, 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)
: Watcher(SyncClient(address, username, password, auth_token_ttl), key,
range_end, fromIndex, callback, wait_callback) {}
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,
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) {}
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, std::string const& key, std::string const& range_end,
int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
std::function<void(bool)> wait_callback, std::function<void(bool)> wait_callback,
int const auth_token_ttl): std::string const& target_name_override)
Watcher(address, username, password, key, range_end, -1, callback, wait_callback, auth_token_ttl) { : Watcher(SyncClient(address, ca, cert, privkey, target_name_override), key,
} range_end, fromIndex, callback, wait_callback) {}
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,
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,
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):
Watcher(SyncClient(address, username, password, auth_token_ttl), key, range_end, fromIndex, callback, wait_callback) {
}
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,
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) {
}
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, etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::function<void(Response)> callback, std::function<void(Response)> callback, bool recursive)
bool recursive): : Watcher(client, key, callback, nullptr, recursive) {}
Watcher(client, key, callback, nullptr, recursive) {
}
etcd::Watcher::Watcher(SyncClient 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, callback, nullptr) { : Watcher(client, key, range_end, callback, nullptr) {}
}
etcd::Watcher::Watcher(SyncClient const &client, std::string const & key, int64_t fromIndex, etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::function<void(Response)> callback, int64_t fromIndex,
bool recursive): std::function<void(Response)> callback, bool recursive)
Watcher(client, key, fromIndex, callback, nullptr, recursive) { : Watcher(client, key, fromIndex, callback, nullptr, recursive) {}
}
etcd::Watcher::Watcher(SyncClient const& client, std::string const& key, etcd::Watcher::Watcher(SyncClient const& client, std::string const& key,
std::string const& range_end, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback): std::function<void(Response)> callback)
Watcher(client, key, range_end, fromIndex, callback, nullptr) { : Watcher(client, key, range_end, fromIndex, callback, nullptr) {}
}
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, std::function<void(Response)> callback, bool recursive)
bool recursive): : Watcher(address, key, callback, nullptr, recursive) {}
Watcher(address, key, callback, nullptr, 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, callback, nullptr) { : Watcher(address, key, range_end, callback, nullptr) {}
}
etcd::Watcher::Watcher(std::string const & address, std::string const & key, int64_t fromIndex, etcd::Watcher::Watcher(std::string const& address, std::string const& key,
std::function<void(Response)> callback, int64_t fromIndex,
bool recursive): std::function<void(Response)> callback, bool recursive)
Watcher(address, key, fromIndex, callback, nullptr, recursive) { : Watcher(address, key, fromIndex, callback, nullptr, 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, int64_t fromIndex, std::string const& range_end, int64_t fromIndex,
std::function<void(Response)> callback): std::function<void(Response)> callback)
Watcher(address, key, range_end, fromIndex, callback, nullptr) { : Watcher(address, key, range_end, fromIndex, callback, nullptr) {}
}
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, 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, std::function<void(Response)> callback,
bool recursive, int const auth_token_ttl)
int const auth_token_ttl): : Watcher(address, username, password, key, range_end, callback, nullptr,
Watcher(address, username, password, key, callback, nullptr, recursive, auth_token_ttl) { 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,
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, std::string const& key, std::string const& range_end,
int64_t fromIndex,
std::function<void(Response)> callback, std::function<void(Response)> callback,
int const auth_token_ttl): std::string const& target_name_override)
Watcher(address, username, password, key, range_end, callback, nullptr, auth_token_ttl) { : Watcher(address, ca, cert, privkey, key, range_end, fromIndex, callback,
} nullptr, target_name_override) {}
etcd::Watcher::Watcher(std::string const & address, etcd::Watcher::~Watcher() { this->Cancel(); }
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, bool etcd::Watcher::Wait() {
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();
@ -256,31 +229,34 @@ 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;
} }
} }
bool etcd::Watcher::Cancel() bool etcd::Watcher::Cancel() {
{
stubs->call->CancelWatch(); stubs->call->CancelWatch();
return this->Wait(); return this->Wait();
} }
bool etcd::Watcher::Cancelled() const bool etcd::Watcher::Cancelled() const {
{
return cancelled.load() || stubs->call->Cancelled(); return cancelled.load() || stubs->call->Cancelled();
} }
void etcd::Watcher::doWatch(std::string const& key, void etcd::Watcher::doWatch(std::string const& key,
std::string const& range_end, std::string const& range_end,
std::string const& auth_token, std::string const& auth_token,
std::function<void(Response)> callback) 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 // n.b.: watch: no need for timeout
@ -298,11 +274,12 @@ void etcd::Watcher::doWatch(std::string const & key,
stubs->call->waitForResponse(callback); stubs->call->waitForResponse(callback);
if (wait_callback != nullptr) { if (wait_callback != nullptr) {
// issue the callback in another thread (detached) to avoid deadlock, // issue the callback in another thread (detached) to avoid deadlock,
// it is ok to detach a pplx::task, but we cannot use the pplx::task // it is ok to detach a pplx::task, but we don't want to use the
// in the core library // pplx::task in the core library
std::thread canceller([this]() { bool cancelled = stubs->call->Cancelled();
wait_callback(stubs->call->Cancelled()); std::function<void(bool)> wait_callback = this->wait_callback;
}); std::thread canceller(
[wait_callback, cancelled]() { wait_callback(cancelled); });
canceller.detach(); canceller.detach();
} }
}); });

View File

@ -1,20 +1,34 @@
#include "etcd/v3/Action.hpp"
#include <grpc/support/log.h> #include <grpc/support/log.h>
#include <grpcpp/support/status.h> #include <grpcpp/support/status.h>
#include "etcd/v3/action_constants.hpp" #include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Action.hpp" #include <cstdlib>
etcdv3::Action::Action(etcdv3::ActionParameters const &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(); this->InitAction();
} }
etcdv3::Action::Action(etcdv3::ActionParameters && params) etcdv3::Action::Action(etcdv3::ActionParameters&& params) {
{
parameters = std::move(params); parameters = std::move(params);
this->InitAction(); this->InitAction();
} }
etcdv3::Action::~Action() {
cq_.Shutdown();
// cancel on-the-fly calls
context.TryCancel();
}
void etcdv3::Action::InitAction() { 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:
@ -25,8 +39,7 @@ void etcdv3::Action::InitAction() {
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;
@ -43,7 +56,8 @@ bool etcdv3::ActionParameters::has_grpc_timeout() const {
return this->grpc_timeout != std::chrono::microseconds::zero(); return this->grpc_timeout != std::chrono::microseconds::zero();
} }
std::chrono::system_clock::time_point etcdv3::ActionParameters::grpc_deadline() const { std::chrono::system_clock::time_point etcdv3::ActionParameters::grpc_deadline()
const {
return std::chrono::system_clock::now() + this->grpc_timeout; return std::chrono::system_clock::now() + this->grpc_timeout;
} }
@ -66,22 +80,28 @@ void etcdv3::ActionParameters::dump(std::ostream &os) const {
os << " grpc_timeout: " << grpc_timeout.count() << "(ms)" << std::endl; os << " grpc_timeout: " << grpc_timeout.count() << "(ms)" << std::endl;
} }
void etcdv3::Action::waitForResponse() void etcdv3::Action::waitForResponse() {
{
void* got_tag; void* got_tag;
bool ok = false; bool ok = false;
if (parameters.has_grpc_timeout()) { if (parameters.has_grpc_timeout()) {
switch (cq_.AsyncNext(&got_tag, &ok, parameters.grpc_deadline())) { switch (cq_.AsyncNext(&got_tag, &ok, parameters.grpc_deadline())) {
case CompletionQueue::NextStatus::TIMEOUT: { case CompletionQueue::NextStatus::TIMEOUT: {
status = grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, "gRPC timeout"); status =
grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, "gRPC timeout");
break; break;
} }
case CompletionQueue::NextStatus::SHUTDOWN: { case CompletionQueue::NextStatus::SHUTDOWN: {
status = grpc::Status(grpc::StatusCode::UNAVAILABLE, "gRPC already shutdown"); status =
grpc::Status(grpc::StatusCode::UNAVAILABLE, "gRPC already shutdown");
break; break;
} }
case CompletionQueue::NextStatus::GOT_EVENT: { case CompletionQueue::NextStatus::GOT_EVENT: {
if (!ok) {
status =
grpc::Status(grpc::StatusCode::ABORTED,
"Failed to execute the action: not ok or invalid tag");
}
break; break;
} }
} }
@ -91,7 +111,8 @@ void etcdv3::Action::waitForResponse()
} }
} }
const std::chrono::high_resolution_clock::time_point etcdv3::Action::startTimepoint() { const std::chrono::high_resolution_clock::time_point
etcdv3::Action::startTimepoint() {
return this->start_timepoint; return this->start_timepoint;
} }
@ -104,6 +125,11 @@ std::string etcdv3::detail::string_plus_one(std::string const &value) {
return s; return s;
} }
} }
return {etcdv3::NUL}; return {etcdv3::NUL};
} }
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,59 +0,0 @@
#include "etcd/v3/AsyncCompareAndDeleteAction.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Transaction.hpp"
using etcdserverpb::RangeRequest;
using etcdserverpb::PutRequest;
using etcdserverpb::RequestOp;
using etcdserverpb::ResponseOp;
using etcdserverpb::TxnRequest;
etcdv3::AsyncCompareAndDeleteAction::AsyncCompareAndDeleteAction(
etcdv3::ActionParameters && params, etcdv3::AtomicityType type)
:etcdv3::Action(std::move(params))
{
etcdv3::Transaction transaction(parameters.key);
if(type == etcdv3::AtomicityType::PREV_VALUE)
{
transaction.init_compare(parameters.old_value,
CompareResult::EQUAL,
CompareTarget::VALUE);
}
else if (type == etcdv3::AtomicityType::PREV_INDEX)
{
transaction.init_compare(parameters.old_revision,
CompareResult::EQUAL,
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;
txn_resp.set_action(etcdv3::COMPAREDELETE_ACTION);
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);
if(!reply.succeeded())
{
txn_resp.set_error_code(ERROR_COMPARE_FAILED);
txn_resp.set_error_message("etcd-cpp-apiv3: compare failed");
}
}
return txn_resp;
}

View File

@ -1,61 +0,0 @@
#include "etcd/v3/AsyncCompareAndSwapAction.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Transaction.hpp"
using etcdserverpb::RangeRequest;
using etcdserverpb::PutRequest;
using etcdserverpb::RequestOp;
using etcdserverpb::ResponseOp;
using etcdserverpb::TxnRequest;
etcdv3::AsyncCompareAndSwapAction::AsyncCompareAndSwapAction(
etcdv3::ActionParameters && params, etcdv3::AtomicityType type)
: etcdv3::Action(std::move(params))
{
etcdv3::Transaction transaction(parameters.key);
if(type == etcdv3::AtomicityType::PREV_VALUE)
{
transaction.init_compare(parameters.old_value,
CompareResult::EQUAL,
CompareTarget::VALUE);
}
else if (type == etcdv3::AtomicityType::PREV_INDEX)
{
transaction.init_compare(parameters.old_revision,
CompareResult::EQUAL,
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;
txn_resp.set_action(etcdv3::COMPARESWAP_ACTION);
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);
//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(ERROR_COMPARE_FAILED);
txn_resp.set_error_message("etcd-cpp-apiv3: compare failed");
}
}
return txn_resp;
}

View File

@ -1,45 +0,0 @@
#include "etcd/v3/AsyncDeleteAction.hpp"
#include "etcd/v3/action_constants.hpp"
using etcdserverpb::DeleteRangeRequest;
etcdv3::AsyncDeleteAction::AsyncDeleteAction(
ActionParameters && params)
: etcdv3::Action(std::move(params))
{
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(etcdv3::NUL));
} 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::AsyncDeleteResponse etcdv3::AsyncDeleteAction::ParseResponse()
{
AsyncDeleteResponse del_resp;
del_resp.set_action(etcdv3::DELETE_ACTION);
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,32 +0,0 @@
#include "etcd/v3/AsyncDeleteResponse.hpp"
#include "etcd/v3/action_constants.hpp"
void etcdv3::AsyncDeleteResponse::ParseResponse(std::string const& key, bool prefix, DeleteRangeResponse& resp)
{
index = resp.header().revision();
if(resp.prev_kvs_size() == 0)
{
error_code = etcdv3::ERROR_KEY_NOT_FOUND;
error_message = "etcd-cpp-apiv3: key not found";
}
else
{
//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();
}
}
}

View File

@ -1,214 +0,0 @@
#include "etcd/v3/AsyncElectionAction.hpp"
#include <grpcpp/support/status.h>
#include "etcd/v3/action_constants.hpp"
using v3electionpb::LeaderKey;
using v3electionpb::CampaignRequest;
using v3electionpb::CampaignResponse;
using v3electionpb::ProclaimRequest;
using v3electionpb::ProclaimResponse;
using v3electionpb::LeaderRequest;
using v3electionpb::LeaderResponse;
using v3electionpb::ResignRequest;
using v3electionpb::ResignResponse;
etcdv3::AsyncCampaignAction::AsyncCampaignAction(
etcdv3::ActionParameters && params)
: etcdv3::Action(std::move(params))
{
CampaignRequest campaign_request;
campaign_request.set_name(parameters.name);
campaign_request.set_lease(parameters.lease_id);
campaign_request.set_value(parameters.value);
response_reader = parameters.election_stub->AsyncCampaign(&context, campaign_request, &cq_);
response_reader->Finish(&reply, &status, (void *)this);
}
etcdv3::AsyncCampaignResponse etcdv3::AsyncCampaignAction::ParseResponse()
{
AsyncCampaignResponse campaign_resp;
campaign_resp.set_action(etcdv3::CAMPAIGN_ACTION);
if(!status.ok()) {
campaign_resp.set_error_code(status.error_code());
campaign_resp.set_error_message(status.error_message());
}
else {
campaign_resp.ParseResponse(reply);
}
return campaign_resp;
}
etcdv3::AsyncProclaimAction::AsyncProclaimAction(
etcdv3::ActionParameters && params)
: etcdv3::Action(std::move(params))
{
auto leader = new LeaderKey();
leader->set_name(parameters.name);
leader->set_key(parameters.key);
leader->set_rev(parameters.revision);
leader->set_lease(parameters.lease_id);
ProclaimRequest proclaim_request;
proclaim_request.set_allocated_leader(leader);
proclaim_request.set_value(parameters.value);
response_reader = parameters.election_stub->AsyncProclaim(&context, proclaim_request, &cq_);
response_reader->Finish(&reply, &status, (void *)this);
}
etcdv3::AsyncProclaimResponse etcdv3::AsyncProclaimAction::ParseResponse()
{
AsyncProclaimResponse proclaim_resp;
proclaim_resp.set_action(etcdv3::PROCLAIM_ACTION);
if(!status.ok()) {
proclaim_resp.set_error_code(status.error_code());
proclaim_resp.set_error_message(status.error_message());
}
else {
proclaim_resp.ParseResponse(reply);
}
return proclaim_resp;
}
etcdv3::AsyncLeaderAction::AsyncLeaderAction(
etcdv3::ActionParameters && params)
: etcdv3::Action(std::move(params))
{
LeaderRequest leader_request;
leader_request.set_name(parameters.name);
response_reader = parameters.election_stub->AsyncLeader(&context, leader_request, &cq_);
response_reader->Finish(&reply, &status, (void *)this);
}
etcdv3::AsyncLeaderResponse etcdv3::AsyncLeaderAction::ParseResponse()
{
AsyncLeaderResponse leader_resp;
leader_resp.set_action(etcdv3::LEADER_ACTION);
if(!status.ok()) {
leader_resp.set_error_code(status.error_code());
leader_resp.set_error_message(status.error_message());
}
else {
leader_resp.ParseResponse(reply);
}
return leader_resp;
}
etcdv3::AsyncObserveAction::AsyncObserveAction(etcdv3::ActionParameters && params)
: etcdv3::Action(std::move(params))
{
LeaderRequest leader_request;
leader_request.set_name(parameters.name);
response_reader = parameters.election_stub->AsyncObserve(&context, leader_request, &cq_, (void *)etcdv3::ELECTION_OBSERVE_CREATE);
void *got_tag;
bool ok = false;
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)etcdv3::ELECTION_OBSERVE_CREATE) {
// n.b.: leave the issue of `Read` to the `waitForResponse`
} else {
throw std::runtime_error("failed to create a observe connection");
}
}
void etcdv3::AsyncObserveAction::waitForResponse()
{
void* got_tag;
bool ok = false;
if (isCancelled.load()) {
status = grpc::Status::CANCELLED;
}
if (!status.ok()) {
return;
}
response_reader->Read(&reply, (void *)this);
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void*)this) {
auto response = ParseResponse();
if (response.get_error_code() == 0) {
// issue the next read
response_reader->Read(&reply, (void *)this);
} else {
this->CancelObserve();
}
} else {
this->CancelObserve();
status = grpc::Status::CANCELLED;
}
}
void etcdv3::AsyncObserveAction::CancelObserve()
{
std::lock_guard<std::mutex> scope_lock(this->protect_is_cancalled);
if (!isCancelled.exchange(true)) {
void* got_tag;
bool ok = false;
response_reader->Finish(&status, (void *)this);
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)this) {
// ok
} else {
std::cerr << "Failed to finish a election observing connection" << std::endl;
}
cq_.Shutdown();
}
}
bool etcdv3::AsyncObserveAction::Cancelled() const {
return isCancelled.load();
}
etcdv3::AsyncObserveResponse etcdv3::AsyncObserveAction::ParseResponse()
{
AsyncObserveResponse leader_resp;
leader_resp.set_action(etcdv3::OBSERVE_ACTION);
if(!status.ok()) {
leader_resp.set_error_code(status.error_code());
leader_resp.set_error_message(status.error_message());
}
else {
leader_resp.ParseResponse(reply);
}
return leader_resp;
}
etcdv3::AsyncResignAction::AsyncResignAction(
etcdv3::ActionParameters && params)
: etcdv3::Action(std::move(params))
{
auto leader = new LeaderKey();
leader->set_name(parameters.name);
leader->set_key(parameters.key);
leader->set_rev(parameters.revision);
leader->set_lease(parameters.lease_id);
ResignRequest resign_request;
resign_request.set_allocated_leader(leader);
response_reader = parameters.election_stub->AsyncResign(&context, resign_request, &cq_);
response_reader->Finish(&reply, &status, (void *)this);
}
etcdv3::AsyncResignResponse etcdv3::AsyncResignAction::ParseResponse()
{
AsyncResignResponse resign_resp;
resign_resp.set_action(etcdv3::RESIGN_ACTION);
if(!status.ok()) {
resign_resp.set_error_code(status.error_code());
resign_resp.set_error_message(status.error_message());
}
else {
resign_resp.ParseResponse(reply);
}
return resign_resp;
}

View File

@ -1,35 +0,0 @@
#include "etcd/v3/AsyncElectionResponse.hpp"
#include "etcd/v3/action_constants.hpp"
using etcdserverpb::ResponseOp;
void etcdv3::AsyncCampaignResponse::ParseResponse(CampaignResponse& reply) {
index = reply.header().revision();
auto const &leader = reply.leader();
name = leader.name();
value.kvs.set_key(leader.key());
value.kvs.set_create_revision(leader.rev());
value.kvs.set_lease(leader.lease());
}
void etcdv3::AsyncProclaimResponse::ParseResponse(ProclaimResponse& reply) {
index = reply.header().revision();
}
void etcdv3::AsyncLeaderResponse::ParseResponse(LeaderResponse& reply) {
index = reply.header().revision();
value.kvs = reply.kv();
}
void etcdv3::AsyncObserveResponse::ParseResponse(LeaderResponse& reply) {
index = reply.header().revision();
value.kvs = reply.kv();
}
void etcdv3::AsyncResignResponse::ParseResponse(ResignResponse& reply) {
index = reply.header().revision();
}

1496
src/v3/AsyncGRPC.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
#include "etcd/v3/AsyncHeadAction.hpp"
#include <cstdlib>
#include "etcd/v3/action_constants.hpp"
using etcdserverpb::RangeRequest;
etcdv3::AsyncHeadAction::AsyncHeadAction(
etcdv3::ActionParameters && params)
: etcdv3::Action(std::move(params))
{
RangeRequest get_request;
get_request.set_key(etcdv3::NUL);
get_request.set_limit(1);
response_reader = parameters.kv_stub->AsyncRange(&context,get_request,&cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncHeadResponse etcdv3::AsyncHeadAction::ParseResponse()
{
AsyncHeadResponse head_resp;
head_resp.set_action(etcdv3::GET_ACTION);
if(!status.ok())
{
head_resp.set_error_code(status.error_code());
head_resp.set_error_message(status.error_message());
}
else
{
head_resp.ParseResponse(reply);
}
return head_resp;
}

View File

@ -1,11 +0,0 @@
#include "etcd/v3/AsyncHeadResponse.hpp"
#include "etcd/v3/action_constants.hpp"
void etcdv3::AsyncHeadResponse::ParseResponse(RangeResponse& resp)
{
cluster_id = resp.header().cluster_id();
member_id = resp.header().member_id();
index = resp.header().revision();
raft_term = resp.header().raft_term();
}

View File

@ -1,259 +0,0 @@
#include "etcd/v3/AsyncLeaseAction.hpp"
#include "etcd/Response.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Transaction.hpp"
#include <grpcpp/support/status.h>
using etcdserverpb::LeaseGrantRequest;
using etcdserverpb::LeaseRevokeRequest;
using etcdserverpb::LeaseCheckpointRequest;
using etcdserverpb::LeaseKeepAliveRequest;
using etcdserverpb::LeaseTimeToLiveRequest;
using etcdserverpb::LeaseLeasesRequest;
etcdv3::AsyncLeaseGrantAction::AsyncLeaseGrantAction(
etcdv3::ActionParameters && params)
: etcdv3::Action(std::move(params))
{
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;
lease_resp.set_action(etcdv3::LEASEGRANT);
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 && params)
: etcdv3::Action(std::move(params))
{
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;
lease_resp.set_action(etcdv3::LEASEREVOKE);
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 && params)
: etcdv3::Action(std::move(params))
{
isCancelled = false;
stream = parameters.lease_stub->AsyncLeaseKeepAlive(&context, &cq_, (void*)etcdv3::KEEPALIVE_CREATE);
void *got_tag = nullptr;
bool ok = false;
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)etcdv3::KEEPALIVE_CREATE) {
// ok
} else {
throw std::runtime_error("Failed to create a lease keep-alive connection");
}
}
etcdv3::AsyncLeaseKeepAliveResponse etcdv3::AsyncLeaseKeepAliveAction::ParseResponse()
{
AsyncLeaseKeepAliveResponse lease_resp;
lease_resp.set_action(etcdv3::LEASEKEEPALIVE);
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) {
status = grpc::Status::CANCELLED;
return etcd::Response(ParseResponse(), etcd::detail::duration_till_now(start_timepoint));
}
LeaseKeepAliveRequest leasekeepalive_request;
leasekeepalive_request.set_id(parameters.lease_id);
void *got_tag = nullptr;
bool ok = false;
if (parameters.has_grpc_timeout()) {
stream->Write(leasekeepalive_request, (void *)etcdv3::KEEPALIVE_WRITE);
// wait write finish
switch (cq_.AsyncNext(&got_tag, &ok, parameters.grpc_deadline())) {
case CompletionQueue::NextStatus::TIMEOUT: {
status = grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, "gRPC timeout during keep alive write");
break;
}
case CompletionQueue::NextStatus::SHUTDOWN: {
status = grpc::Status(grpc::StatusCode::UNAVAILABLE, "gRPC already shutdown during keep alive write");
break;
}
case CompletionQueue::NextStatus::GOT_EVENT: {
if (!ok || got_tag != (void *)etcdv3::KEEPALIVE_WRITE) {
return etcd::Response(grpc::StatusCode::ABORTED, "Failed to create a lease keep-alive connection: write not ok or invalid tag");
}
}
}
if (!status.ok()) {
this->CancelKeepAlive();
return etcd::Response(ParseResponse(), etcd::detail::duration_till_now(start_timepoint));
}
stream->Read(&reply, (void*)etcdv3::KEEPALIVE_READ);
// wait read finish
switch (cq_.AsyncNext(&got_tag, &ok, parameters.grpc_deadline())) {
case CompletionQueue::NextStatus::TIMEOUT: {
status = grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, "gRPC timeout during keep alive read");
break;
}
case CompletionQueue::NextStatus::SHUTDOWN: {
status = grpc::Status(grpc::StatusCode::UNAVAILABLE, "gRPC already shutdown during keep alive read");
break;
}
case CompletionQueue::NextStatus::GOT_EVENT: {
if (!ok || got_tag != (void *)etcdv3::KEEPALIVE_READ) {
return etcd::Response(grpc::StatusCode::ABORTED, "Failed to create a lease keep-alive connection: read not ok or invalid tag");
}
break;
}
}
this->CancelKeepAlive();
return etcd::Response(ParseResponse(), etcd::detail::duration_till_now(start_timepoint));
} else {
stream->Write(leasekeepalive_request, (void *)etcdv3::KEEPALIVE_WRITE);
// wait write finish
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)etcdv3::KEEPALIVE_WRITE) {
stream->Read(&reply, (void*)etcdv3::KEEPALIVE_READ);
// wait read finish
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)etcdv3::KEEPALIVE_READ) {
return etcd::Response(ParseResponse(), etcd::detail::duration_till_now(start_timepoint));
}
}
this->CancelKeepAlive();
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;
void *got_tag = nullptr;
bool ok = false;
stream->WritesDone((void*)etcdv3::KEEPALIVE_DONE);
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)etcdv3::KEEPALIVE_DONE) {
// ok
} else {
std::cerr << "Failed to mark a lease keep-alive connection as DONE" << std::endl;
}
grpc::Status status;
stream->Finish(&status, (void *)this);
if (cq_.Next(&got_tag, &ok) && ok && got_tag == (void *)this) {
// ok
} else {
std::cerr << "Failed to finish a lease keep-alive connection" << std::endl;
}
cq_.Shutdown();
}
}
bool etcdv3::AsyncLeaseKeepAliveAction::Cancelled() const
{
return isCancelled;
}
etcdv3::ActionParameters& etcdv3::AsyncLeaseKeepAliveAction::mutable_parameters() {
return this->parameters;
}
etcdv3::AsyncLeaseTimeToLiveAction::AsyncLeaseTimeToLiveAction(
etcdv3::ActionParameters && params)
: etcdv3::Action(std::move(params))
{
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;
lease_resp.set_action(etcdv3::LEASETIMETOLIVE);
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 && params)
: etcdv3::Action(std::move(params))
{
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;
lease_resp.set_action(etcdv3::LEASELEASES);
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,34 +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();
for (auto lease : resp.leases()) {
leases.emplace_back(lease.id());
}
}

View File

@ -1,64 +0,0 @@
#include "etcd/v3/AsyncLockAction.hpp"
#include "etcd/v3/action_constants.hpp"
using v3lockpb::LockRequest;
using v3lockpb::UnlockRequest;
etcdv3::AsyncLockAction::AsyncLockAction(
ActionParameters && params)
: etcdv3::Action(std::move(params))
{
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;
lock_resp.set_action(etcdv3::LOCK_ACTION);
if(!status.ok())
{
lock_resp.set_error_code(status.error_code());
lock_resp.set_error_message(status.error_message());
}
else
{
lock_resp.ParseResponse(reply);
}
return lock_resp;
}
etcdv3::AsyncUnlockAction::AsyncUnlockAction(
ActionParameters && params)
: etcdv3::Action(std::move(params))
{
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;
unlock_resp.set_action(etcdv3::UNLOCK_ACTION);
if(!status.ok())
{
unlock_resp.set_error_code(status.error_code());
unlock_resp.set_error_message(status.error_message());
}
else
{
unlock_resp.ParseResponse(reply);
}
return unlock_resp;
}

View File

@ -1,13 +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,36 +0,0 @@
#include "etcd/v3/AsyncPutAction.hpp"
#include "etcd/v3/action_constants.hpp"
using etcdserverpb::PutRequest;
etcdv3::AsyncPutAction::AsyncPutAction(
ActionParameters && params)
: etcdv3::Action(std::move(params))
{
PutRequest put_request;
put_request.set_key(parameters.key);
put_request.set_value(parameters.value);
put_request.set_lease(parameters.lease_id);
put_request.set_prev_kv(true);
response_reader = parameters.kv_stub->AsyncPut(&context, put_request, &cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncPutResponse etcdv3::AsyncPutAction::ParseResponse()
{
AsyncPutResponse put_resp;
put_resp.set_action(etcdv3::PUT_ACTION);
if(!status.ok())
{
put_resp.set_error_code(status.error_code());
put_resp.set_error_message(status.error_message());
}
else
{
put_resp.ParseResponse(reply);
}
return put_resp;
}

View File

@ -1,13 +0,0 @@
#include "etcd/v3/AsyncPutResponse.hpp"
#include "etcd/v3/action_constants.hpp"
void etcdv3::AsyncPutResponse::ParseResponse(PutResponse& resp)
{
index = resp.header().revision();
//get all previous values
etcdv3::KeyValue kv;
kv.kvs.CopyFrom(resp.prev_kv());
prev_value = kv;
}

View File

@ -1,56 +0,0 @@
#include "etcd/v3/AsyncRangeAction.hpp"
#include <cstdlib>
#include "etcd/v3/action_constants.hpp"
using etcdserverpb::RangeRequest;
etcdv3::AsyncRangeAction::AsyncRangeAction(
etcdv3::ActionParameters && params)
: etcdv3::Action(std::move(params))
{
RangeRequest get_request;
if (parameters.key.empty()) {
get_request.set_key(etcdv3::NUL);
} else {
get_request.set_key(parameters.key);
}
get_request.set_limit(parameters.limit);
if(parameters.withPrefix)
{
if (parameters.key.empty()) {
get_request.set_range_end(detail::string_plus_one(etcdv3::NUL));
} 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);
// set keys_only and count_only
get_request.set_keys_only(params.keys_only);
get_request.set_count_only(params.count_only);
response_reader = parameters.kv_stub->AsyncRange(&context,get_request,&cq_);
response_reader->Finish(&reply, &status, (void*)this);
}
etcdv3::AsyncRangeResponse etcdv3::AsyncRangeAction::ParseResponse()
{
AsyncRangeResponse range_resp;
range_resp.set_action(etcdv3::GET_ACTION);
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,29 +0,0 @@
#include "etcd/v3/AsyncRangeResponse.hpp"
#include "etcd/v3/action_constants.hpp"
void etcdv3::AsyncRangeResponse::ParseResponse(RangeResponse& resp, bool prefix)
{
index = resp.header().revision();
if(resp.kvs_size() == 0 && !prefix)
{
error_code = etcdv3::ERROR_KEY_NOT_FOUND;
error_message = "etcd-cpp-apiv3: 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,51 +0,0 @@
#include "etcd/v3/AsyncSetAction.hpp"
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/Transaction.hpp"
etcdv3::AsyncSetAction::AsyncSetAction(
etcdv3::ActionParameters && params, bool create)
: etcdv3::Action(std::move(params))
{
etcdv3::Transaction transaction(parameters.key);
isCreate = create;
transaction.init_compare(CompareResult::EQUAL,
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;
txn_resp.set_action(isCreate? etcdv3::CREATE_ACTION : etcdv3::SET_ACTION);
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);
if(!reply.succeeded() && isCreate)
{
txn_resp.set_error_code(etcdv3::ERROR_KEY_ALREADY_EXISTS);
txn_resp.set_error_message("etcd-cpp-apiv3: key already exists");
}
}
return txn_resp;
}

View File

@ -1,38 +0,0 @@
#include "etcd/v3/action_constants.hpp"
#include "etcd/v3/AsyncTxnAction.hpp"
#include "etcd/v3/Transaction.hpp"
etcdv3::AsyncTxnAction::AsyncTxnAction(
etcdv3::ActionParameters && params, etcdv3::Transaction const &tx)
: etcdv3::Action(std::move(params))
{
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;
txn_resp.set_action(etcdv3::TXN_ACTION);
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);
//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(ERROR_COMPARE_FAILED);
txn_resp.set_error_message("etcd-cpp-apiv3: compare failed");
}
}
return txn_resp;
}

View File

@ -1,48 +0,0 @@
#include "etcd/v3/AsyncTxnResponse.hpp"
#include "etcd/v3/AsyncRangeResponse.hpp"
#include "etcd/v3/AsyncDeleteResponse.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())
{
AsyncDeleteResponse 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/action_constants.hpp"
#include "etcd/v3/AsyncRangeResponse.hpp"
#include "etcd/v3/Transaction.hpp"
using etcdserverpb::RangeRequest;
using etcdserverpb::PutRequest;
using etcdserverpb::RequestOp;
using etcdserverpb::ResponseOp;
using etcdserverpb::TxnRequest;
etcdv3::AsyncUpdateAction::AsyncUpdateAction(
etcdv3::ActionParameters && params)
: etcdv3::Action(std::move(params))
{
etcdv3::Transaction transaction(parameters.key);
transaction.init_compare(CompareResult::GREATER,
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(etcdv3::ERROR_KEY_NOT_FOUND);
txn_resp.set_error_message("etcd-cpp-apiv3: key not found");
}
}
return txn_resp;
}

View File

@ -1,188 +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 && params)
: etcdv3::Action(std::move(params))
{
isCancelled.store(false);
stream = parameters.watch_stub->AsyncWatch(&context,&cq_,(void*)etcdv3::WATCH_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(etcdv3::NUL));
} 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 *)etcdv3::WATCH_CREATE) {
stream->Write(watch_req, (void *)etcdv3::WATCH_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 *)etcdv3::WATCH_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(isCancelled.load()) {
break;
}
if(got_tag == (void*)etcdv3::WATCH_WRITES_DONE) {
isCancelled.store(true);
cq_.Shutdown();
break;
}
if(got_tag == (void*)this) // read tag
{
if (reply.canceled()) {
isCancelled.store(true);
cq_.Shutdown();
break;
}
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*)etcdv3::WATCH_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;
}
break;
}
else
{
// otherwise, start next round read-reply
stream->Read(&reply, (void*)this);
}
}
}
}
void etcdv3::AsyncWatchAction::CancelWatch()
{
if (!isCancelled.exchange(true)) {
stream->WritesDone((void*)etcdv3::WATCH_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(isCancelled.load()) {
break;
}
if(got_tag == (void*)etcdv3::WATCH_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) {
auto resp = etcd::Response(grpc::StatusCode::OUT_OF_RANGE /* error code */,
"required revision has been compacted");
resp._compact_revision = reply.compact_revision();
callback(resp);
}
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;
watch_resp.set_action(etcdv3::WATCH_ACTION);
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,46 +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";
compact_revision = reply.compact_revision();
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,11 +2,7 @@
#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;
using etcdserverpb::RequestOp;
using etcdserverpb::DeleteRangeRequest;
namespace etcdv3 { namespace etcdv3 {
@ -20,182 +16,345 @@ static etcdserverpb::Compare::CompareTarget to_compare_target(CompareTarget t) {
return static_cast<etcdserverpb::Compare::CompareTarget>(static_cast<int>(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::reset_key(std::string const& newkey) { void etcdv3::Transaction::add_compare_version(std::string const& key,
key = newkey; int64_t const& version,
std::string const& range_end) {
this->add_compare_version(key, CompareResult::EQUAL, version, range_end);
} }
void etcdv3::Transaction::init_compare(CompareResult result, CompareTarget target){ void etcdv3::Transaction::add_compare_version(std::string const& key,
Compare* compare = txn_request->add_compare(); CompareResult const& result,
int64_t const& version,
std::string const& range_end) {
auto compare = txn_request->add_compare();
compare->set_result(detail::to_compare_result(result)); compare->set_result(detail::to_compare_result(result));
compare->set_target(detail::to_compare_target(target)); compare->set_target(detail::to_compare_target(CompareTarget::VERSION));
compare->set_key(key); compare->set_key(key);
compare->set_version(version);
compare->set_version(0); compare->set_range_end(range_end);
} }
void etcdv3::Transaction::init_compare(std::string const& old_value, CompareResult result, CompareTarget target){ void etcdv3::Transaction::add_compare_create(std::string const& key,
Compare* compare = txn_request->add_compare(); int64_t const& create_revision,
std::string const& range_end) {
this->add_compare_create(key, CompareResult::EQUAL, create_revision,
range_end);
}
void etcdv3::Transaction::add_compare_create(std::string const& key,
CompareResult const& result,
int64_t const& create_revision,
std::string const& range_end) {
auto compare = txn_request->add_compare();
compare->set_result(detail::to_compare_result(result)); compare->set_result(detail::to_compare_result(result));
compare->set_target(detail::to_compare_target(target)); compare->set_target(detail::to_compare_target(CompareTarget::CREATE));
compare->set_key(key); compare->set_key(key);
compare->set_create_revision(create_revision);
compare->set_value(old_value); compare->set_range_end(range_end);
} }
void etcdv3::Transaction::init_compare(int64_t old_index, CompareResult result, CompareTarget target){ void etcdv3::Transaction::add_compare_mod(std::string const& key,
Compare* compare = txn_request->add_compare(); int64_t const& mod_revision,
std::string const& range_end) {
this->add_compare_mod(key, CompareResult::EQUAL, mod_revision, range_end);
}
void etcdv3::Transaction::add_compare_mod(std::string const& key,
CompareResult const& result,
int64_t const& mod_revision,
std::string const& range_end) {
auto compare = txn_request->add_compare();
compare->set_result(detail::to_compare_result(result)); compare->set_result(detail::to_compare_result(result));
compare->set_target(detail::to_compare_target(target)); compare->set_target(detail::to_compare_target(CompareTarget::MOD));
compare->set_key(key); compare->set_key(key);
compare->set_mod_revision(mod_revision);
compare->set_mod_revision(old_index); compare->set_range_end(range_end);
} }
/** void etcdv3::Transaction::add_compare_value(std::string const& key,
* get key on failure std::string const& value,
*/ std::string const& range_end) {
void etcdv3::Transaction::setup_basic_failure_operation(std::string const& key) { this->add_compare_value(key, CompareResult::EQUAL, value, range_end);
std::unique_ptr<RangeRequest> get_request(new RangeRequest());
get_request->set_key(key);
RequestOp* req_failure = txn_request->add_failure();
req_failure->set_allocated_request_range(get_request.release());
} }
/** void etcdv3::Transaction::add_compare_value(std::string const& key,
* get key on failure, get key before put, modify and then get updated key CompareResult const& result,
*/ std::string const& value,
void etcdv3::Transaction::setup_set_failure_operation(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();
compare->set_result(detail::to_compare_result(result));
compare->set_target(detail::to_compare_target(CompareTarget::VALUE));
compare->set_key(key);
compare->set_value(value);
compare->set_range_end(range_end);
}
void etcdv3::Transaction::add_compare_lease(std::string const& key,
int64_t const& lease,
std::string const& range_end) {
this->add_compare_lease(key, CompareResult::EQUAL, lease, range_end);
}
void etcdv3::Transaction::add_compare_lease(std::string const& key,
CompareResult const& result,
int64_t const& lease,
std::string const& range_end) {
auto compare = txn_request->add_compare();
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::add_success_range(std::string const& key,
std::string const& range_end,
bool const recursive,
const int64_t limit) {
auto succ = txn_request->add_success();
auto get_request = succ->mutable_request_range();
etcdv3::detail::make_request_with_ranges(*get_request, key, range_end,
recursive);
get_request->set_limit(limit);
}
void etcdv3::Transaction::add_success_put(std::string const& key,
std::string const& value,
int64_t const leaseid,
const bool prev_kv) {
auto succ = txn_request->add_success();
auto put_request = succ->mutable_request_put();
put_request->set_key(key); put_request->set_key(key);
put_request->set_value(value); put_request->set_value(value);
put_request->set_prev_kv(true); put_request->set_prev_kv(prev_kv);
put_request->set_lease(leaseid); 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_success_delete(std::string const& key,
* add key and then get new value of key std::string const& range_end,
*/ bool const recursive,
void etcdv3::Transaction::setup_basic_create_sequence(std::string const& key, std::string const& value, int64_t leaseid) { const bool prev_kv) {
std::unique_ptr<PutRequest> put_request(new PutRequest()); 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_key(key);
put_request->set_value(value); put_request->set_value(value);
put_request->set_prev_kv(true); put_request->set_prev_kv(prev_kv);
put_request->set_lease(leaseid); 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_failure_delete(std::string const& key,
* get key value then modify and get new value std::string const& range_end,
*/ bool const recursive,
void etcdv3::Transaction::setup_compare_and_swap_sequence(std::string const& value, int64_t leaseid) { const bool prev_kv) {
std::unique_ptr<PutRequest> put_request(new PutRequest()); auto fail = txn_request->add_failure();
put_request->set_key(key); auto del_request = fail->mutable_request_delete_range();
put_request->set_value(value); etcdv3::detail::make_request_with_ranges(*del_request, key, range_end,
put_request->set_prev_kv(true); recursive);
put_request->set_lease(leaseid); del_request->set_prev_kv(prev_kv);
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_failure_txn(
* get key, delete const std::shared_ptr<Transaction> txn) {
*/ auto fail = txn_request->add_failure();
void etcdv3::Transaction::setup_delete_sequence(std::string const &key, std::string const &range_end, bool recursive) { auto txn_request = fail->mutable_request_txn();
std::unique_ptr<DeleteRangeRequest> del_request(new DeleteRangeRequest()); txn_request->CopyFrom(*txn->txn_request);
del_request->set_key(key);
del_request->set_prev_kv(true);
if(recursive)
{
del_request->set_range_end(range_end);
} }
RequestOp* req_success = txn_request->add_success(); void etcdv3::Transaction::setup_compare_and_create(
req_success->set_allocated_request_delete_range(del_request.release()); 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,
* get key, delete std::string const& prev_value,
*/ std::string const& create_key,
void etcdv3::Transaction::setup_delete_failure_operation(std::string const &key, std::string const &range_end, bool recursive) { std::string const& value,
std::unique_ptr<RangeRequest> get_request(new RangeRequest()); int64_t const leaseid) {
std::unique_ptr<DeleteRangeRequest> del_request(new DeleteRangeRequest()); this->add_compare_value(key, CompareResult::NOT_EQUAL, prev_value);
get_request.reset(new RangeRequest()); this->add_success_put(create_key, value, leaseid);
get_request->set_key(key); this->add_failure_range(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(); void etcdv3::Transaction::setup_compare_and_swap(std::string const& key,
req_failure->set_allocated_request_delete_range(del_request.release()); 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_and_delete_operation(std::string const& key) { void etcdv3::Transaction::setup_compare_or_swap(std::string const& key,
std::unique_ptr<DeleteRangeRequest> del_request(new DeleteRangeRequest()); std::string const& prev_value,
del_request->set_key(key); std::string const& value,
del_request->set_prev_kv(true); int64_t const leaseid) {
RequestOp* req_success = txn_request->add_success(); this->add_compare_value(key, CompareResult::NOT_EQUAL, prev_value);
req_success->set_allocated_request_delete_range(del_request.release()); this->add_success_put(key, value, leaseid);
this->add_failure_range(key);
} }
void etcdv3::Transaction::setup_put(std::string const &key, std::string const &value) { void etcdv3::Transaction::setup_compare_and_delete(
std::unique_ptr<PutRequest> put_request(new PutRequest()); std::string const& key, std::string const& prev_value,
put_request->set_key(key); std::string const& delete_key, std::string const& range_end,
put_request->set_value(value); const bool recursive) {
put_request->set_prev_kv(false); this->add_compare_value(key, CompareResult::EQUAL, prev_value);
RequestOp* req_success = txn_request->add_success(); this->add_success_delete(delete_key, range_end, recursive,
req_success->set_allocated_request_put(put_request.release()); 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) { void etcdv3::Transaction::setup_delete(std::string const& key) {
std::unique_ptr<DeleteRangeRequest> del_request(new DeleteRangeRequest()); this->add_success_delete(key, "", false,
del_request->set_key(key); true /* for backwards compatibility */);
del_request->set_prev_kv(false);
RequestOp* req_success = txn_request->add_success();
req_success->set_allocated_request_delete_range(del_request.release());
} }
etcdv3::Transaction::~Transaction() { 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,69 +1,56 @@
#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;
} }
int64_t 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;
} }
int64_t etcdv3::V3Response::get_compact_revision() const void etcdv3::V3Response::set_compact_revision(const int64_t compact_revision) {
{ this->compact_revision = compact_revision;
return 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) { void etcdv3::V3Response::set_lock_key(std::string const& key) {
@ -78,26 +65,22 @@ void etcdv3::V3Response::set_name(std::string const &name) {
this->name = name; this->name = name;
} }
std::string const & etcdv3::V3Response::get_name() const { std::string const& etcdv3::V3Response::get_name() const { return this->name; }
return this->name;
}
std::vector<mvccpb::Event> const& etcdv3::V3Response::get_events() const { std::vector<mvccpb::Event> const& etcdv3::V3Response::get_events() const {
return this->events; return this->events;
} }
uint64_t etcdv3::V3Response::get_cluster_id() const { uint64_t etcdv3::V3Response::get_cluster_id() const { return this->cluster_id; }
return this->cluster_id;
}
uint64_t etcdv3::V3Response::get_member_id() const { uint64_t etcdv3::V3Response::get_member_id() const { return this->member_id; }
return this->member_id;
}
uint64_t etcdv3::V3Response::get_raft_term() const { uint64_t etcdv3::V3Response::get_raft_term() const { return this->raft_term; }
return this->raft_term;
}
std::vector<int64_t> const& etcdv3::V3Response::get_leases() const { std::vector<int64_t> const& etcdv3::V3Response::get_leases() const {
return this->leases; return this->leases;
} }
std::vector<etcdv3::Member> const& etcdv3::V3Response::get_members() const {
return this->members;
}

View File

@ -5,7 +5,7 @@ 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::PUT_ACTION = "put"; char const* etcdv3::PUT_ACTION = "set"; // alias
char const* etcdv3::DELETE_ACTION = "delete"; char const* etcdv3::DELETE_ACTION = "delete";
char const* etcdv3::COMPAREDELETE_ACTION = "compareAndDelete"; char const* etcdv3::COMPAREDELETE_ACTION = "compareAndDelete";
char const* etcdv3::LOCK_ACTION = "lock"; char const* etcdv3::LOCK_ACTION = "lock";
@ -19,26 +19,34 @@ char const * etcdv3::LEASEKEEPALIVE = "leasekeepalive";
char const* etcdv3::LEASETIMETOLIVE = "leasetimetolive"; char const* etcdv3::LEASETIMETOLIVE = "leasetimetolive";
char const* etcdv3::LEASELEASES = "leaseleases"; 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::CAMPAIGN_ACTION = "campaign";
char const* etcdv3::PROCLAIM_ACTION = "preclaim"; char const* etcdv3::PROCLAIM_ACTION = "preclaim";
char const* etcdv3::LEADER_ACTION = "leader"; char const* etcdv3::LEADER_ACTION = "leader";
char const* etcdv3::OBSERVE_ACTION = "obverse"; char const* etcdv3::OBSERVE_ACTION = "obverse";
char const* etcdv3::RESIGN_ACTION = "resign"; char const* etcdv3::RESIGN_ACTION = "resign";
// see: noPrefixEnd in etcd, however c++ doesn't allows '\0' inside a string, thus we use // see: noPrefixEnd in etcd, however c++ doesn't allows naive '\0' inside
// the UTF-8 char U+0000 (i.e., "\xC0\x80"). // a string, thus we use std::string(1, '\x00') as the constructor.
char const * etcdv3::NUL = "\xC0\x80"; std::string const etcdv3::NUL = std::string(1, '\x00');
char const* etcdv3::KEEPALIVE_CREATE = "keepalive create"; char const* etcdv3::KEEPALIVE_CREATE = "keepalive create";
char const* etcdv3::KEEPALIVE_WRITE = "keepalive write"; char const* etcdv3::KEEPALIVE_WRITE = "keepalive write";
char const* etcdv3::KEEPALIVE_READ = "keepalive read"; char const* etcdv3::KEEPALIVE_READ = "keepalive read";
char const* etcdv3::KEEPALIVE_DONE = "keepalive done"; 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_CREATE = "watch create";
char const* etcdv3::WATCH_WRITE = "watch write"; 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_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_CREATE = "observe create";
char const* etcdv3::ELECTION_OBSERVE_FINISH = "observe finish";
const int etcdv3::ERROR_GRPC_OK = 0; const int etcdv3::ERROR_GRPC_OK = 0;
const int etcdv3::ERROR_GRPC_CANCELLED = 1; const int etcdv3::ERROR_GRPC_CANCELLED = 1;

View File

@ -5,16 +5,15 @@
#include "etcd/Client.hpp" #include "etcd/Client.hpp"
static const std::string etcd_url("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("setup with auth") TEST_CASE("setup with auth") {
{
etcd::Client* etcd = etcd::Client::WithUser(etcd_url, "root", "root"); etcd::Client* etcd = etcd::Client::WithUser(etcd_url, "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(etcd_url, "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();
@ -28,24 +27,28 @@ TEST_CASE("add a new key after authenticate")
CHECK(0 < val.modified_index()); CHECK(0 < val.modified_index());
CHECK(1 == val.version()); CHECK(1 == val.version());
CHECK(0 < resp.index()); CHECK(0 < resp.index());
CHECK(etcd::ERROR_KEY_ALREADY_EXISTS == etcd->add("/test/key1", "43").get().error_code()); // Key already exists CHECK(
CHECK(etcd::ERROR_KEY_ALREADY_EXISTS == etcd->add("/test/key1", "42").get().error_code()); // Key already exists etcd::ERROR_KEY_ALREADY_EXISTS ==
CHECK("etcd-cpp-apiv3: 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(etcd_url, "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(etcd_url, "root", "root");
REQUIRE(0 == etcd->rmdir("/test", true).get().error_code()); REQUIRE(0 == etcd->rmdir("/test", true).get().error_code());
} }

View File

@ -18,12 +18,21 @@ foreach(testfile ${TEST_FILES})
else() else()
add_executable(${test_name} EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/${testfile}) add_executable(${test_name} EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/${testfile})
endif() endif()
use_cxx11(${test_name}) 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}>)
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)
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}) 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();
}

View File

@ -8,16 +8,15 @@
#include "etcd/Client.hpp" #include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp" #include "etcd/KeepAlive.hpp"
static const std::string etcd_url("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("setup") TEST_CASE("setup") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
etcd.rmdir("/test", true).wait(); etcd.rmdir("/test", true).wait();
} }
TEST_CASE("campaign and resign") TEST_CASE("campaign and resign") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
auto keepalive = etcd.leasekeepalive(60).get(); auto keepalive = etcd.leasekeepalive(60).get();
@ -36,9 +35,9 @@ TEST_CASE("campaign and resign")
} }
// proclaim // proclaim
auto resp3 = etcd.proclaim("test", lease_id, auto resp3 = etcd.proclaim("test", lease_id, resp1.value().key(),
resp1.value().key(), resp1.value().created_index(), resp1.value().created_index(), "tttt")
"tttt").get(); .get();
REQUIRE(0 == resp3.error_code()); REQUIRE(0 == resp3.error_code());
// leader // leader
@ -50,13 +49,67 @@ TEST_CASE("campaign and resign")
} }
// resign // resign
auto resp5 = etcd.resign("test", lease_id, auto resp5 = etcd.resign("test", lease_id, resp1.value().key(),
resp1.value().key(), resp1.value().created_index()).get(); resp1.value().created_index())
.get();
REQUIRE(0 == resp5.error_code()); REQUIRE(0 == resp5.error_code());
} }
TEST_CASE("cleanup") 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::Client etcd(etcd_url);
etcd.rmdir("/test", true).get(); 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,22 +5,29 @@
#include "etcd/SyncClient.hpp" #include "etcd/SyncClient.hpp"
static const std::string etcd_url("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_url);
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(etcd::ERROR_KEY_ALREADY_EXISTS == 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(etcd::ERROR_KEY_NOT_FOUND == 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
@ -32,7 +39,8 @@ TEST_CASE("sync operations")
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(etcd::ERROR_KEY_NOT_FOUND == 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());
@ -51,28 +59,37 @@ TEST_CASE("sync operations")
CHECK(2 == etcd.keys("/test/new_dir").keys().size()); CHECK(2 == etcd.keys("/test/new_dir").keys().size());
// rmdir // rmdir
CHECK(etcd::ERROR_KEY_NOT_FOUND == 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");
int64_t index = etcd.modify_if("/test/key1", "43", "42").index(); index = etcd.modify_if("/test/key1", "43", "42").index();
CHECK(etcd::ERROR_COMPARE_FAILED == etcd.modify_if("/test/key1", "44", "42").error_code()); 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(etcd::ERROR_COMPARE_FAILED == 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(etcd::ERROR_COMPARE_FAILED == 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(etcd::ERROR_COMPARE_FAILED == 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());
@ -88,6 +105,7 @@ TEST_CASE("sync operations")
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());
res = etcd.get("/test/key1");
CHECK(leaseid == res.value().lease()); CHECK(leaseid == res.value().lease());
// modify with lease // modify with lease
@ -114,8 +132,7 @@ TEST_CASE("sync operations")
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_url);
etcd.set("/test/key1", "42"); etcd.set("/test/key1", "42");
@ -132,8 +149,7 @@ 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_url);
std::thread watch_thrd1([&]() { std::thread watch_thrd1([&]() {
@ -159,8 +175,7 @@ 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_url);
int64_t index = etcd.set("/test/key1", "42").index(); int64_t index = etcd.set("/test/key1", "42").index();

View File

@ -7,16 +7,15 @@
#include "etcd/Client.hpp" #include "etcd/Client.hpp"
static const std::string etcd_url("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("setup") TEST_CASE("setup") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
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(etcd_url);
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();
@ -30,42 +29,58 @@ TEST_CASE("add a new key")
CHECK(0 < val.modified_index()); CHECK(0 < val.modified_index());
CHECK(1 == val.version()); CHECK(1 == val.version());
CHECK(0 < resp.index()); CHECK(0 < resp.index());
CHECK(etcd::ERROR_KEY_ALREADY_EXISTS == etcd.add("/test/key1", "43").get().error_code()); // Key already exists CHECK(etcd::ERROR_KEY_ALREADY_EXISTS ==
CHECK(etcd::ERROR_KEY_ALREADY_EXISTS == etcd.add("/test/key1", "42").get().error_code()); // Key already exists etcd.add("/test/key1", "43").get().error_code()); // Key already exists
CHECK("etcd-cpp-apiv3: 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(etcd_url);
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(etcd_url);
CHECK("42" == etcd.get("/test/key1").get().value().as_string()); CHECK("42" == etcd.get("/test/key1").get().value().as_string());
CHECK(etcd::ERROR_KEY_NOT_FOUND == 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(etcd_url);
etcd::Response resp = etcd.modify("/test/key1", "43").get();
// get
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 REQUIRE(0 == resp.error_code()); // overwrite
CHECK("update" == resp.action()); CHECK("update" == resp.action());
CHECK(etcd::ERROR_KEY_NOT_FOUND == 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(etcd_url);
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
@ -76,16 +91,16 @@ TEST_CASE("set a key")
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(etcd_url);
etcd.set("/test/key1", "42").wait(); etcd.set("/test/key1", "42").wait();
@ -101,15 +116,14 @@ TEST_CASE("atomic compare-and-swap")
CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code()); CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code());
CHECK("etcd-cpp-apiv3: 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(etcd::ERROR_KEY_NOT_FOUND == res.error_code()); CHECK(etcd::ERROR_COMPARE_FAILED == res.error_code());
CHECK("etcd-cpp-apiv3: 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(etcd_url);
etcd::Response resp = etcd.rm("/test/key11111").get(); etcd::Response resp = etcd.rm("/test/key11111").get();
CHECK(!resp.is_ok()); CHECK(!resp.is_ok());
@ -131,15 +145,14 @@ TEST_CASE("delete a value")
CHECK(modify_index == resp.prev_value().modified_index()); CHECK(modify_index == resp.prev_value().modified_index());
CHECK(version == resp.prev_value().version()); 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(version == resp.value().version()); CHECK(version == resp.value().version());
CHECK("" == resp.value().as_string()); CHECK("43" == resp.value().as_string());
CHECK("/test/key1" == resp.value().key()); 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(etcd_url);
etcd.set("/test/key1", "42").wait(); etcd.set("/test/key1", "42").wait();
@ -154,8 +167,7 @@ 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(etcd_url);
int64_t index = etcd.set("/test/key1", "42").get().index(); int64_t index = etcd.set("/test/key1", "42").get().index();
@ -170,8 +182,7 @@ 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(etcd_url);
etcd.set("/test/key1", "42").wait(); etcd.set("/test/key1", "42").wait();
@ -201,8 +212,7 @@ TEST_CASE("deep atomic compare-and-swap")
CHECK("etcd-cpp-apiv3: compare failed" == res.error_message()); CHECK("etcd-cpp-apiv3: compare failed" == res.error_message());
} }
TEST_CASE("using binary keys and values, raw char pointer doesn't work") TEST_CASE("using binary keys and values, raw char pointer doesn't work") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
etcd.rmdir("/test", true).wait(); etcd.rmdir("/test", true).wait();
{ {
@ -223,7 +233,7 @@ TEST_CASE("using binary keys and values, raw char pointer doesn't work")
CHECK(std::string("42\0foo", 6) != resp.value().as_string()); CHECK(std::string("42\0foo", 6) != resp.value().as_string());
} }
{ {
// should exist // should exist, but different value
etcd::Response resp = etcd.get("/test/key1\0xyz").get(); etcd::Response resp = etcd.get("/test/key1\0xyz").get();
REQUIRE(resp.is_ok()); REQUIRE(resp.is_ok());
CHECK(std::string("42") == resp.value().as_string()); CHECK(std::string("42") == resp.value().as_string());
@ -231,12 +241,13 @@ TEST_CASE("using binary keys and values, raw char pointer doesn't work")
} }
} }
TEST_CASE("using binary keys and values, std::string is ok for \\0") TEST_CASE("using binary keys and values, std::string is ok for \\0") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
etcd.rmdir("/test", true).wait(); etcd.rmdir("/test", true).wait();
{ {
etcd::Response resp = etcd.put(std::string("/test/key1\0xyz", 14), std::string("42\0foo", 6)).get(); etcd::Response resp =
etcd.put(std::string("/test/key1\0xyz", 14), std::string("42\0foo", 6))
.get();
REQUIRE(resp.is_ok()); REQUIRE(resp.is_ok());
} }
{ {
@ -254,8 +265,7 @@ TEST_CASE("using binary keys and values, std::string is ok for \\0")
} }
} }
TEST_CASE("list a directory") TEST_CASE("list a directory") {
{
etcd::Client etcd(etcd_url); 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());
@ -283,11 +293,15 @@ 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(etcd_url);
CHECK(0 == etcd.ls("/test/new_dir").get().keys().size()); CHECK(0 == etcd.ls("/test/new_dir").get().keys().size());
@ -297,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());
@ -315,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());
@ -326,8 +345,7 @@ 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("list by range, w/o values") TEST_CASE("list by range, w/o values") {
{
etcd::Client etcd(etcd_url); 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());
@ -337,14 +355,16 @@ TEST_CASE("list by range, w/o values")
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/key2").get(); etcd::Response resp1 =
etcd.ls("/test/new_dir/key1", "/test/new_dir/key2").get();
REQUIRE(resp1.is_ok()); REQUIRE(resp1.is_ok());
CHECK("get" == resp1.action()); CHECK("get" == resp1.action());
REQUIRE(1 == resp1.keys().size()); REQUIRE(1 == resp1.keys().size());
REQUIRE(1 == resp1.values().size()); REQUIRE(1 == resp1.values().size());
REQUIRE(resp1.values()[0].as_string() == "value1"); REQUIRE(resp1.values()[0].as_string() == "value1");
etcd::Response resp2 = etcd.keys("/test/new_dir/key1", "/test/new_dir/key2").get(); etcd::Response resp2 =
etcd.keys("/test/new_dir/key1", "/test/new_dir/key2").get();
REQUIRE(resp1.is_ok()); REQUIRE(resp1.is_ok());
CHECK("get" == resp2.action()); CHECK("get" == resp2.action());
REQUIRE(1 == resp2.keys().size()); REQUIRE(1 == resp2.keys().size());
@ -354,15 +374,15 @@ TEST_CASE("list by range, w/o values")
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("delete a directory") {
{
etcd::Client etcd(etcd_url); 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(etcd::ERROR_KEY_NOT_FOUND == 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();
@ -373,7 +393,6 @@ 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(etcd::ERROR_KEY_NOT_FOUND == resp.error_code()); CHECK(etcd::ERROR_KEY_NOT_FOUND == resp.error_code());
@ -385,11 +404,24 @@ TEST_CASE("delete a directory")
CHECK("etcd-cpp-apiv3: 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.rmdir("", true).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();
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); etcd::Client etcd(etcd_url);
CHECK(etcd::ERROR_KEY_NOT_FOUND == 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();
etcd.set("/test/new_dir/key1", "value1").wait(); etcd.set("/test/new_dir/key1", "value1").wait();
@ -406,8 +438,7 @@ 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(etcd_url);
etcd.set("/test/key1", "42").wait(); etcd.set("/test/key1", "42").wait();
@ -424,8 +455,7 @@ 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(etcd_url);
pplx::task<etcd::Response> res = etcd.watch("/test", true); pplx::task<etcd::Response> res = etcd.watch("/test", true);
@ -451,8 +481,7 @@ 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(etcd_url);
REQUIRE(0 == etcd.rmdir("/test", true).get().error_code()); REQUIRE(0 == etcd.rmdir("/test", true).get().error_code());
int64_t index = etcd.set("/test/key1", "42").get().index(); int64_t index = etcd.set("/test/key1", "42").get().index();
@ -478,8 +507,7 @@ 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(etcd_url);
REQUIRE(0 == etcd.rmdir("/test", true).get().error_code()); REQUIRE(0 == etcd.rmdir("/test", true).get().error_code());
int64_t index = etcd.set("/test/key1", "42").get().index(); int64_t index = etcd.set("/test/key1", "42").get().index();
@ -510,7 +538,8 @@ TEST_CASE("watch multiple keys and use promise") {
int64_t 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 =
etcd.watch("/test", start_index, true)
.then([](pplx::task<etcd::Response> const& resp_task) -> size_t { .then([](pplx::task<etcd::Response> const& resp_task) -> size_t {
auto const& resp = resp_task.get(); auto const& resp = resp_task.get();
return resp.events().size(); return resp.events().size();
@ -519,8 +548,7 @@ TEST_CASE("watch multiple keys and use promise") {
CHECK(2 == event_size); CHECK(2 == event_size);
} }
TEST_CASE("lease grant") TEST_CASE("lease grant") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
etcd::Response res = etcd.leasegrant(60).get(); etcd::Response res = etcd.leasegrant(60).get();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
@ -531,6 +559,8 @@ TEST_CASE("lease grant")
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());
// change with lease id // change with lease id
@ -539,6 +569,8 @@ TEST_CASE("lease grant")
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
@ -595,8 +627,7 @@ TEST_CASE("lease grant")
CHECK("etcdserver: requested lease not found" == res.error_message()); CHECK("etcdserver: requested lease not found" == res.error_message());
} }
TEST_CASE("lease list") TEST_CASE("lease list") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
etcd::Response res = etcd.leasegrant(60).get(); etcd::Response res = etcd.leasegrant(60).get();
REQUIRE(res.is_ok()); REQUIRE(res.is_ok());
@ -615,8 +646,7 @@ TEST_CASE("lease list")
} }
} }
TEST_CASE("cleanup") TEST_CASE("cleanup") {
{
etcd::Client etcd(etcd_url); 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,10 @@
#include "etcd/Client.hpp" #include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp" #include "etcd/KeepAlive.hpp"
static const std::string etcd_url("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("lock and unlock") TEST_CASE("lock and unlock") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
// lock // lock
@ -29,8 +29,7 @@ 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(etcd_url);
// lock // lock
@ -62,7 +61,8 @@ TEST_CASE("double lock will fail")
REQUIRE(0 == resp3.error_code()); REQUIRE(0 == resp3.error_code());
// create a duration // create a duration
// using a duration longer than default lease TTL for lock (see: DEFAULT_LEASE_TTL_FOR_LOCK) // using a duration longer than default lease TTL for lock (see:
// 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
@ -84,8 +84,7 @@ TEST_CASE("double lock will fail")
REQUIRE(0 == resp5.error_code()); REQUIRE(0 == resp5.error_code());
} }
TEST_CASE("lock could be timeout") TEST_CASE("lock could be timeout") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
// setup the timeout // setup the timeout
@ -113,13 +112,14 @@ TEST_CASE("lock could be timeout")
REQUIRE(0 == resp5.error_code()); REQUIRE(0 == resp5.error_code());
} }
TEST_CASE("lock using lease") TEST_CASE("lock using lease") {
{
etcd::Client etcd(etcd_url); 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
std::function<void(std::exception_ptr)> handler =
[&failed](std::exception_ptr eptr) {
try { try {
if (eptr) { if (eptr) {
std::rethrow_exception(eptr); std::rethrow_exception(eptr);
@ -129,6 +129,9 @@ TEST_CASE("lock using lease")
failed = true; failed = true;
} }
}; };
#else
std::function<void(std::exception_ptr)> handler;
#endif
// with handler // with handler
{ {
@ -197,23 +200,30 @@ TEST_CASE("lock using lease")
} }
} }
TEST_CASE("concurrent lock & unlock") TEST_CASE("concurrent lock & unlock") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
std::string const lock_key = "/test/test_key"; std::string const lock_key = "/test/test_key";
constexpr size_t trials = 192; 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 =
[&etcd](std::string const& key, const size_t index) {
std::cout << "start lock for " << key << ", index is " << index
<< std::endl;
auto resp = etcd.lock(key).get(); auto resp = etcd.lock(key).get();
std::cout << "lock for " << index << " is ok, starts sleeping: ..." << resp.error_message() << std::endl << std::flush; std::cout << "lock for " << index << " is ok, starts sleeping: ..."
<< resp.error_message() << std::endl
<< std::flush;
REQUIRE(resp.is_ok()); REQUIRE(resp.is_ok());
std::srand(index); std::srand(index);
size_t time_to_sleep = 1; size_t time_to_sleep = 1;
std::this_thread::sleep_for(std::chrono::seconds(time_to_sleep)); 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; std::cout << "lock for " << index << " resumes from sleep: ..."
<< resp.error_message() << std::endl
<< std::flush;
REQUIRE(etcd.unlock(resp.lock_key()).get().is_ok()); REQUIRE(etcd.unlock(resp.lock_key()).get().is_ok());
std::cout << "thread " << index << " been unlocked" << std::endl << std::flush; std::cout << "thread " << index << " been unlocked" << std::endl
<< std::flush;
}; };
std::vector<std::thread> locks(trials); std::vector<std::thread> locks(trials);
@ -232,12 +242,15 @@ TEST_CASE("concurrent lock & unlock with a put in between") {
constexpr size_t trials = 128; 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::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; std::cout << "start lock for " << index << std::endl;
auto resp = etcd.lock(key, true).get(); auto resp = etcd.lock(key, true).get();
std::cout << "lock for " << index << " is ok, start put and unlock: ..." << resp.error_message() << std::endl; std::cout << "lock for " << index << " is ok, start put and unlock: ..."
<< resp.error_message() << std::endl;
REQUIRE(resp.is_ok()); REQUIRE(resp.is_ok());
auto put_resp = etcd.put("/test/test_put", "hello" + std::to_string(index)).get(); auto put_resp =
etcd.put("/test/test_put", "hello" + std::to_string(index)).get();
REQUIRE(put_resp.is_ok()); REQUIRE(put_resp.is_ok());
REQUIRE(etcd.unlock(resp.lock_key()).get().is_ok()); REQUIRE(etcd.unlock(resp.lock_key()).get().is_ok());
std::cout << "thread " << index << " been unlocked" << std::endl; std::cout << "thread " << index << " been unlocked" << std::endl;

View File

@ -6,16 +6,14 @@
#include "etcd/Client.hpp" #include "etcd/Client.hpp"
#include "etcd/KeepAlive.hpp" #include "etcd/KeepAlive.hpp"
static const std::string etcd_url("http://127.0.0.1:2379"); static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
class DistributedLock { class DistributedLock {
public: public:
DistributedLock(const std::string &lock_name, DistributedLock(const std::string& lock_name, uint timeout = 0);
uint timeout = 0);
~DistributedLock() noexcept; ~DistributedLock() noexcept;
inline bool lock_acquired() { inline bool lock_acquired() { return _acquired; }
return _acquired;
}
private: private:
bool _acquired = false; bool _acquired = false;
@ -23,11 +21,9 @@ private:
std::unique_ptr<::etcd::Client> _etcd_client; std::unique_ptr<::etcd::Client> _etcd_client;
}; };
DistributedLock::DistributedLock(const std::string &lock_name, DistributedLock::DistributedLock(const std::string& lock_name, uint timeout) {
uint timeout) {
_etcd_client = std::unique_ptr<etcd::Client>(new etcd::Client(etcd_url)); _etcd_client = std::unique_ptr<etcd::Client>(new etcd::Client(etcd_url));
try {
if (timeout == 0) { if (timeout == 0) {
etcd::Response resp = _etcd_client->lock(lock_name).get(); etcd::Response resp = _etcd_client->lock(lock_name).get();
if (resp.is_ok()) { if (resp.is_ok()) {
@ -48,14 +44,12 @@ DistributedLock::DistributedLock(const std::string &lock_name,
_acquired = true; _acquired = true;
} }
} else if (status == std::future_status::timeout) { } else if (status == std::future_status::timeout) {
std::cerr << "failed to acquire distributed because of lock timeout" << std::endl; std::cerr << "failed to acquire distributed because of lock timeout"
<< std::endl;
} else { } else {
std::cerr << "failed to acquire distributed lock" << std::endl; std::cerr << "failed to acquire distributed lock" << std::endl;
} }
} }
} catch (std::exception &e) {
std::cerr << "failed to construct: " << e.what() << std::endl;
}
} }
DistributedLock::~DistributedLock() noexcept { DistributedLock::~DistributedLock() noexcept {
@ -63,19 +57,15 @@ DistributedLock::~DistributedLock() noexcept {
return; return;
} }
try {
auto resp = _etcd_client->unlock(_lock_key).get(); auto resp = _etcd_client->unlock(_lock_key).get();
if (!resp.is_ok()) { if (!resp.is_ok()) {
std::cout << resp.error_code() << std::endl; std::cout << resp.error_code() << std::endl;
} }
} catch (std::exception &e) {
std::cerr << "failed to destruct: " << e.what() << std::endl;
}
} }
int main() { int main() {
int i = 0, t = 0; int i = 0, t = 0;
while(t < 10 /* update this value to make it run for longer */) { while (t < 100 /* update this value to make it run for longer */) {
{ {
DistributedLock lock(std::to_string(i), 0); DistributedLock lock(std::to_string(i), 0);
if (!lock.lock_acquired()) { if (!lock.lock_acquired()) {
@ -88,6 +78,8 @@ int main() {
if (i == 10) { if (i == 10) {
i = 0; i = 0;
} }
if (t % 10 == 0) {
std::cout << "round: i = " << i << ", t = " << t << std::endl; 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;
}

View File

@ -8,44 +8,60 @@
#include "etcd/SyncClient.hpp" #include "etcd/SyncClient.hpp"
#include "etcd/Watcher.hpp" #include "etcd/Watcher.hpp"
static const std::string etcd_url("http://127.0.0.1:2379"); static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("http://127.0.0.1:2379");
static int watcher_called = 0; static int watcher_called = 0;
void print_response(etcd::Response const & resp) void print_response(etcd::Response const& resp) {
{
++watcher_called; ++watcher_called;
std::cout << "print response called" << std::endl; 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 << resp.error_code() << ": " << resp.error_message() << std::endl;
} } else {
else {
std::cout << resp.action() << " " << resp.value().as_string() << std::endl; std::cout << 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; std::cout << "Events size: " << resp.events().size() << std::endl;
for (auto const& ev : resp.events()) { for (auto const& ev : resp.events()) {
std::cout << "Value change in events: " << static_cast<int>(ev.event_type()) std::cout << "Value change in events: "
<< ", prev kv = " << ev.prev_kv().key() << " -> " << ev.prev_kv().as_string() << static_cast<int>(ev.event_type())
<< ", kv = " << ev.kv().key() << " -> " << ev.kv().as_string() << ", prev kv = " << ev.prev_kv().key() << " -> "
<< std::endl; << ev.prev_kv().as_string() << ", kv = " << ev.kv().key()
<< " -> " << ev.kv().as_string() << std::endl;
} }
} }
} }
void wait_for_connection(etcd::Client &client) { void wait_for_connection(std::string endpoints) {
// wait until the client connects to etcd server // wait until the client connects to etcd server
while (!client.head().get().is_ok()) { 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); sleep(1);
} }
} }
void initialize_watcher(const std::string& endpoints, void initialize_watcher(const std::string& endpoints, const std::string& prefix,
const std::string& prefix,
std::function<void(etcd::Response)> callback, std::function<void(etcd::Response)> callback,
std::shared_ptr<etcd::Watcher>& watcher) { std::shared_ptr<etcd::Watcher>& watcher) {
// wait until the endpoints turn to be available
wait_for_connection(endpoints);
etcd::Client client(endpoints); etcd::Client client(endpoints);
wait_for_connection(client);
// Check if the failed one has been cancelled first // Check if the failed one has been cancelled first
if (watcher && watcher->Cancelled()) { if (watcher && watcher->Cancelled()) {
@ -55,18 +71,19 @@ void initialize_watcher(const std::string& endpoints,
watcher.reset(new etcd::Watcher(client, prefix, callback, true)); watcher.reset(new etcd::Watcher(client, prefix, callback, true));
// Note that lambda requires `mutable`qualifier. // Note that lambda requires `mutable`qualifier.
watcher->Wait([endpoints, prefix, callback, watcher->Wait(
[endpoints, prefix, callback,
/* By reference for renewing */ &watcher](bool cancelled) mutable { /* By reference for renewing */ &watcher](bool cancelled) mutable {
if (cancelled) { if (cancelled) {
std::cout << "watcher's reconnect loop stopped as been cancelled" << std::endl; std::cout << "watcher's reconnect loop stopped as been cancelled"
<< std::endl;
return; return;
} }
initialize_watcher(endpoints, prefix, callback, watcher); initialize_watcher(endpoints, prefix, callback, watcher);
}); });
} }
TEST_CASE("watch should can be re-established") TEST_CASE("watch should can be re-established") {
{
const std::string my_prefix = "/test"; const std::string my_prefix = "/test";
// the watcher initialized in this way will auto re-connect to etcd // the watcher initialized in this way will auto re-connect to etcd
@ -75,13 +92,19 @@ TEST_CASE("watch should can be re-established")
// issue some changes to see if the watcher works // issue some changes to see if the watcher works
for (int round = 0; round < 100000; ++round) { for (int round = 0; round < 100000; ++round) {
#ifndef _ETCD_NO_EXCEPTIONS
try { try {
etcd::Client client(etcd_url); etcd::Client client(etcd_url);
auto response = client.set( auto response =
my_prefix + "/foo", "bar-" + std::to_string(round)).get(); client.set(my_prefix + "/foo", "bar-" + std::to_string(round)).get();
} catch (...) { } catch (...) {
// pass // 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)); std::this_thread::sleep_for(std::chrono::seconds(2));
} }
@ -91,13 +114,19 @@ TEST_CASE("watch should can be re-established")
// the watcher has been cancelled and shouldn't work anymore // the watcher has been cancelled and shouldn't work anymore
for (int round = 10; round < 20; ++round) { for (int round = 10; round < 20; ++round) {
#ifndef _ETCD_NO_EXCEPTIONS
try { try {
etcd::Client client(etcd_url); etcd::Client client(etcd_url);
auto response = client.set( auto response =
my_prefix + "/foo", "bar-" + std::to_string(round)).get(); client.set(my_prefix + "/foo", "bar-" + std::to_string(round)).get();
} catch (...) { } catch (...) {
// pass // 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)); std::this_thread::sleep_for(std::chrono::seconds(2));
} }

View File

@ -9,16 +9,15 @@ 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";
static const std::string etcd_url("https://127.0.0.1:2379"); static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("https://127.0.0.1:2379");
TEST_CASE("setup with auth") TEST_CASE("setup with auth") {
{
etcd::Client* etcd = etcd::Client::WithSSL(etcd_url, ca, cert, key); 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(etcd_url, 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();
@ -32,24 +31,28 @@ TEST_CASE("add a new key after authenticate")
CHECK(0 < val.modified_index()); CHECK(0 < val.modified_index());
CHECK(1 == val.version()); CHECK(1 == val.version());
CHECK(0 < resp.index()); CHECK(0 < resp.index());
CHECK(etcd::ERROR_KEY_ALREADY_EXISTS == etcd->add("/test/key1", "43").get().error_code()); // Key already exists CHECK(
CHECK(etcd::ERROR_KEY_ALREADY_EXISTS == etcd->add("/test/key1", "42").get().error_code()); // Key already exists etcd::ERROR_KEY_ALREADY_EXISTS ==
CHECK("etcd-cpp-apiv3: 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(etcd_url, 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(etcd_url, ca, cert, key);
REQUIRE(0 == etcd->rmdir("/test", true).get().error_code()); REQUIRE(0 == etcd->rmdir("/test", true).get().error_code());
} }

View File

@ -8,16 +8,15 @@
#include "etcd/Client.hpp" #include "etcd/Client.hpp"
#include "etcd/v3/Transaction.hpp" #include "etcd/v3/Transaction.hpp"
static const std::string etcd_url("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("setup") TEST_CASE("setup") {
{
etcd::Client etcd(etcd_url); etcd::Client etcd(etcd_url);
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(etcd_url);
etcd.rmdir("/test", true).wait(); etcd.rmdir("/test", true).wait();
@ -51,11 +50,8 @@ TEST_CASE("add a new key")
etcdv3::Transaction txn; etcdv3::Transaction txn;
// setup the conditions // setup the conditions
txn.reset_key("/test/x1"); txn.add_compare_value("/test/x1", "1");
txn.init_compare("1", etcdv3::CompareResult::EQUAL, etcdv3::CompareTarget::VALUE); txn.add_compare_value("/test/x2", "2");
txn.reset_key("/test/x2");
txn.init_compare("2", etcdv3::CompareResult::EQUAL, etcdv3::CompareTarget::VALUE);
txn.setup_put("/test/x1", "111"); txn.setup_put("/test/x1", "111");
txn.setup_delete("/test/x2"); txn.setup_delete("/test/x2");
@ -83,8 +79,52 @@ TEST_CASE("add a new key")
} }
} }
TEST_CASE("cleanup") 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); etcd::Client etcd(etcd_url);
REQUIRE(0 == etcd.rmdir("/test", true).get().error_code()); REQUIRE(0 == etcd.rmdir("/test", true).get().error_code());
} }

View File

@ -4,121 +4,228 @@
#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("http://127.0.0.1:2379"); static const std::string etcd_url =
etcdv3::detail::resolve_etcd_endpoints("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 << resp.action() << " " << resp.value().as_string() << std::endl; std::cout << "Watcher " << resp.watch_id() << " responses with "
std::cout << "Previous value: " << resp.prev_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 << "Events size: " << resp.events().size() << std::endl; std::cout << "Events size: " << resp.events().size() << std::endl;
for (auto const& ev : resp.events()) { for (auto const& ev : resp.events()) {
std::cout << "Value change in events: " << static_cast<int>(ev.event_type()) if (ev.prev_kv().key().find("/leader") == 0 ||
<< ", prev kv = " << ev.prev_kv().key() << " -> " << ev.prev_kv().as_string() ev.kv().key().find("/leader") == 0) {
<< ", kv = " << ev.kv().key() << " -> " << ev.kv().as_string() return;
<< std::endl; }
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.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");
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::SyncClient etcd(etcd_url);
etcd.rmdir("/test", true); etcd.rmdir("/test", true);
watcher_called = 0; watcher_called = 0;
etcd::Watcher watcher(etcd_url, "/test", printResponse, true); etcd::Watcher watcher(etcd_url, "/test", printResponse, true);
std::this_thread::sleep_for(std::chrono::seconds(3)); 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_url);
etcd.rmdir("/test", true); etcd.rmdir("/test", true);
watcher_called = 0; watcher_called = 0;
etcd::Watcher watcher(etcd_url, "/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_url);
etcd.rmdir("/test", true);
watcher_called = 0;
{
etcd::Watcher watcher(etcd_url, "/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_url, "/test", printResponse, true); etcd::Watcher watcher(etcd_url, "/test", printResponse, true);
watcher.Cancel(); watcher.Cancel();
} }
TEST_CASE("watch should can be cancelled repeatedly") TEST_CASE("watch should can be cancelled repeatedly") {
{
// cancal immediately after start watch.
etcd::Watcher watcher(etcd_url, "/test", printResponse, true); etcd::Watcher watcher(etcd_url, "/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_url); // etcd::Client etcd(etcd_url);

View File

@ -1,27 +0,0 @@
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO etcd-cpp-apiv3/etcd-cpp-apiv3
REF 2e38d3c11e02366906328d5905ac934311761cd0
SHA512 ca6613b821643050701cbfcb7f9f35c48f0ea9a9d86b73b0401cbacf66a50a1c98b14e07fe8f0939dba379ceb2cf6c60093158b1b4a20c731984c47875b97f90
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()
vcpkg_fixup_cmake_targets(CONFIG_PATH lib/cmake/etcd-cpp-api)
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/share)
vcpkg_copy_pdbs()
# Handle copyright
file(INSTALL ${SOURCE_PATH}/LICENSE.txt DESTINATION ${CURRENT_PACKAGES_DIR}/share/etcd-cpp-apiv3 RENAME copyright)

View File

@ -1,16 +0,0 @@
{
"name": "etcd-cpp-apiv3",
"version-string": "0.2.8",
"description": "The etcd-cpp-apiv3 is a C++ API for etcd's v3 client API, i.e., ETCDCTL_API=3.",
"homepage": "https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3",
"dependencies": [
"boost-asio",
"boost-random",
"boost-system",
"boost-thread",
"cpprestsdk",
"grpc",
"openssl",
"protobuf"
]
}