mirror of https://github.com/etcd-io/jetcd.git
Compare commits
No commits in common. "main" and "jetcd-0.6.1" have entirely different histories.
main
...
jetcd-0.6.
|
@ -6,17 +6,13 @@ labels: ''
|
|||
assignees: ''
|
||||
|
||||
---
|
||||
**Versions**
|
||||
- etcd: _add the etcd version here_
|
||||
- jetcd: _add the jetcd version here_
|
||||
- java: _add the java version here_
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps or reproducer to reproduce the behavior in a form of a unit test.
|
||||
This section *must* be provided, if not, the issue may not get attention since the maintainers have very limited capacity.
|
||||
Steps or reproducer to reproduce the behavior.
|
||||
This section *must* be provided, if not, the issue may not get attention.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
|
|
@ -1,18 +1,11 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "19:30"
|
||||
timezone: Europe/Paris
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "19:30"
|
||||
timezone: Europe/Paris
|
||||
labels:
|
||||
- area/dependencies
|
||||
ignore:
|
||||
- dependency-name: com.google.protobuf:protoc
|
||||
- package-ecosystem: maven
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: org.codehaus.plexus:plexus-compiler-javac-errorprone
|
||||
versions:
|
||||
- "> 2.8.6, < 2.9"
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
#
|
||||
# Copyright 2016-2023 The jetcd authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
name: Build and Publish (Snapshot)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
uses: ./.github/workflows/checks.yml
|
||||
|
||||
build:
|
||||
needs:
|
||||
- checks
|
||||
strategy:
|
||||
matrix:
|
||||
java-version:
|
||||
- 11
|
||||
- 17
|
||||
- 21
|
||||
etcd:
|
||||
- quay.io/coreos/etcd:v3.5.21
|
||||
- quay.io/coreos/etcd:v3.6.0
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
javaVersion: "${{ matrix.java-version }}"
|
||||
etcdImage: "${{ matrix.etcd }}"
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: 'Set Up Java'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
- name: 'Collect Info'
|
||||
run: |
|
||||
./gradlew currentVersion
|
||||
- name: 'Publish Snapshot'
|
||||
if: github.event_name != 'schedule'
|
||||
env:
|
||||
NEXUS_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
NEXUS_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
|
||||
run: |
|
||||
./gradlew publishToSonatype -Prelease.forceSnapshot
|
|
@ -0,0 +1,46 @@
|
|||
#
|
||||
# Copyright 2016-2021 The jetcd authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
name: Build and Publish
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'temurin'
|
||||
- name: Build Project
|
||||
env:
|
||||
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
|
||||
run: |
|
||||
./gradlew spotlessCheck
|
||||
./gradlew publishToSonatype
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright 2016-2023 The jetcd authors
|
||||
# Copyright 2016-2021 The jetcd authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -17,35 +17,27 @@
|
|||
name: Build PR
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
uses: ./.github/workflows/checks.yml
|
||||
|
||||
build:
|
||||
needs:
|
||||
- checks
|
||||
strategy:
|
||||
matrix:
|
||||
java-version:
|
||||
- 11
|
||||
- 17
|
||||
- 21
|
||||
etcd:
|
||||
- quay.io/coreos/etcd:v3.5.21
|
||||
- quay.io/coreos/etcd:v3.6.0
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
javaVersion: "${{ matrix.java-version }}"
|
||||
etcdImage: "${{ matrix.etcd }}"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'temurin'
|
||||
- name: Build Project
|
||||
run: |
|
||||
./gradlew spotlessCheck
|
||||
./gradlew test
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
name: Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
javaVersion:
|
||||
required: true
|
||||
type: string
|
||||
etcdImage:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Set Up Java'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: ${{ inputs.javaVersion }}
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
- name: 'Build Project'
|
||||
env:
|
||||
ETCD_IMAGE: ${{ inputs.etcdImage }}
|
||||
run: |
|
||||
export TC_USER="$(id -u):$(id -g)"
|
||||
echo "tc user -> $TC_USER"
|
||||
|
||||
./gradlew test --info
|
|
@ -1,37 +0,0 @@
|
|||
name: Checks
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: 'Set Up Java'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
- name: 'Run check task'
|
||||
run: |
|
||||
./gradlew check -x test
|
||||
- name: 'Run check script'
|
||||
run: |
|
||||
bash ./.github/workflows/scripts/precheck.sh
|
||||
shell: bash
|
||||
|
||||
deps:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v4
|
|
@ -1,55 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
failed=0
|
||||
|
||||
# Check trailing whitespace
|
||||
files=$(find . -type f \
|
||||
-not -path "./.git/*" \
|
||||
-not -path "*/.gradle/*" \
|
||||
-not -path "*/.idea/*" \
|
||||
-not -path "*/.vscode/*" \
|
||||
-not -path "*/build/*" \
|
||||
-not -path "*/out/*" \
|
||||
-not -path "*/bin/*" \
|
||||
-not -name "*.jar" \
|
||||
-not -name "*.java" \
|
||||
-exec grep -E -l " +$" {} \;)
|
||||
|
||||
count=0
|
||||
|
||||
for file in $files; do
|
||||
((count++))
|
||||
echo "$file"
|
||||
done
|
||||
|
||||
if [ $count -ne 0 ]; then
|
||||
failed=1
|
||||
echo "Error: trailing whitespace(s) in the above $count file(s)"
|
||||
fi
|
||||
|
||||
# Check newline
|
||||
files=$(find . -type f -size +0c \
|
||||
-not -path "./.git/*" \
|
||||
-not -path "*/.gradle/*" \
|
||||
-not -path "*/.idea/*" \
|
||||
-not -path "*/.vscode/*" \
|
||||
-not -path "*/build/*" \
|
||||
-not -path "*/out/*" \
|
||||
-not -path "*/bin/*" \
|
||||
-not -name "*.jar" \
|
||||
-not -name "*.java" \
|
||||
-exec bash -c 'if [[ $(tail -c1 "$0" | wc -l) -eq 0 ]]; then echo "$0"; fi' {} \;)
|
||||
|
||||
count=0
|
||||
|
||||
for file in $files; do
|
||||
((count++))
|
||||
echo "$file"
|
||||
done
|
||||
|
||||
if [ $count -ne 0 ]; then
|
||||
failed=1
|
||||
echo "Error: no newline in the above $count file(s)"
|
||||
fi
|
||||
|
||||
exit $failed
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright 2016-2023 The jetcd authors
|
||||
# Copyright 2016-2021 The jetcd authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -24,19 +24,18 @@ jobs:
|
|||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 60
|
||||
days-before-close: 7
|
||||
only-labels: 'waiting-for-feedbacks'
|
||||
stale-issue-label: stale
|
||||
exempt-issue-labels: never-stale
|
||||
exempt-issue-label: never-stale
|
||||
stale-pr-label: stale
|
||||
exempt-pr-labels: never-stale
|
||||
exempt-pr-label: never-stale
|
||||
stale-issue-message: |
|
||||
This issue is stale because it has been open 60 days with no activity.
|
||||
Remove stale label or comment or this will be closed in 7 days.
|
||||
stale-pr-message: |
|
||||
This PR is stale because it has been open 60 days with no activity.
|
||||
Remove stale label or comment or this will be closed in 7 days.
|
||||
Remove stale label or comment or this will be closed in 7 days.
|
|
@ -15,4 +15,3 @@ logs/
|
|||
.sdkmanrc
|
||||
build/
|
||||
bin/
|
||||
out/
|
||||
|
|
|
@ -4,7 +4,7 @@ jetcd is Apache 2.0 licensed and accepts contributions via GitHub pull requests.
|
|||
|
||||
# Email and chat
|
||||
|
||||
- Email: [etcd-dev](https://groups.google.com/g/etcd-dev)
|
||||
- Email: [etcd-dev](https://groups.google.com/forum/?hl=en#!forum/etcd-dev)
|
||||
- IRC: #[etcd](irc://irc.freenode.org:6667/#etcd) IRC channel on freenode.org
|
||||
|
||||
## Getting started
|
||||
|
@ -25,7 +25,7 @@ This is a rough outline of what a contributor's workflow looks like:
|
|||
- Make sure commit messages are in the proper format (see below).
|
||||
- Push changes in a topic branch to a personal fork of the repository.
|
||||
- Submit a pull request to etcd-io/jetcd.
|
||||
- The PR must receive a LGTM from at least one maintainer found in the [OWNERS](https://github.com/etcd-io/jetcd/blob/main/OWNERS) file.
|
||||
- The PR must receive a LGTM from at least one maintainer found in the [MAINTAINERS](https://github.com/etcd-io/etcd/blob/master/MAINTAINERS) file.
|
||||
|
||||
Thanks for contributing!
|
||||
|
||||
|
@ -40,7 +40,7 @@ Please follow this style to make jetcd easy to review, maintain, and develop.
|
|||
To make sure CI checks would pass please run
|
||||
|
||||
```bash
|
||||
./gradlew spotlessApply
|
||||
./mvnw license:format
|
||||
```
|
||||
|
||||
and including any changes in PR before opening it.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Fanmin Shi <fanmin.shi@coreos.com> (@fanminshi) pkg:*
|
||||
Luca Burgazzoli <lburgazzoli@gmail.com> (@lburgazzoli) pkg:*
|
||||
Xiang Li <xiang.li@coreos.com> (@xiang90) pkg:*
|
||||
Anthony Romano <anthony.romano@coreos.com> (@heyitsanthony) pkg:*
|
||||
|
6
OWNERS
6
OWNERS
|
@ -1,6 +0,0 @@
|
|||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- fanminshi # Fanmin Shi <fanmin.shi@coreos.com>
|
||||
- lburgazzoli # Luca Burgazzoli <lburgazzoli@gmail.com>
|
||||
- vorburger # Michael Vorburger <mike@vorburger.ch>
|
17
README.md
17
README.md
|
@ -1,5 +1,5 @@
|
|||
# jetcd - A Java Client for etcd
|
||||
[](https://github.com/etcd-io/jetcd/actions)
|
||||
[](https://github.com/etcd-io/jetcd/actions)
|
||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
[](https://search.maven.org/#search%7Cga%7C1%7Cio.etcd)
|
||||
[](https://javadoc.io/doc/io.etcd/jetcd-core)
|
||||
|
@ -23,13 +23,13 @@ Java 11 or above is required.
|
|||
</dependency>
|
||||
```
|
||||
|
||||
Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/io/etcd/).
|
||||
Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/io/etcd).
|
||||
|
||||
### Gradle
|
||||
|
||||
```
|
||||
dependencies {
|
||||
implementation "io.etcd:jetcd-core:$jetcd-version"
|
||||
compile "io.etcd:jetcd-core:$jetcd-version"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -63,7 +63,6 @@ GetResponse response = getFuture.get();
|
|||
// delete the key
|
||||
kvClient.delete(key).get();
|
||||
```
|
||||
To build one ssl secured client, refer to [secured client config](docs/SslConfig.md).
|
||||
|
||||
For full etcd v3 API, plesase refer to the [official API documentation](https://etcd.io/docs/current/learning/api/).
|
||||
|
||||
|
@ -80,7 +79,7 @@ import io.etcd.jetcd.Client;
|
|||
import io.etcd.jetcd.test.EtcdClusterExtension;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
@RegisterExtension
|
||||
@RegisterExtension
|
||||
public static final EtcdClusterExtension cluster = EtcdClusterExtension.builder()
|
||||
.withNodes(1)
|
||||
.build();
|
||||
|
@ -99,7 +98,7 @@ The current major version is zero (0.y.z). Anything may change at any time. The
|
|||
|
||||
## Build from source
|
||||
|
||||
The project can be built with [Gradle](https://gradle.org/):
|
||||
The project can be built with [Apache Maven](https://maven.apache.org/):
|
||||
|
||||
```
|
||||
./gradlew compileJava
|
||||
|
@ -111,7 +110,7 @@ The project is tested against a three node `etcd` setup started with the Launche
|
|||
|
||||
```sh
|
||||
$ ./gradlew test
|
||||
```
|
||||
````
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
|
@ -119,7 +118,7 @@ It recommmonds building the project before running tests so that you have artifa
|
|||
|
||||
## Contact
|
||||
|
||||
* Mailing list: [etcd-dev](https://groups.google.com/g/etcd-dev)
|
||||
* Mailing list: [etcd-dev](https://groups.google.com/forum/?hl=en#!forum/etcd-dev)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -128,5 +127,3 @@ See [CONTRIBUTING](https://github.com/etcd-io/jetcd/blob/master/CONTRIBUTING.md)
|
|||
## License
|
||||
|
||||
jetcd is under the Apache 2.0 license. See the [LICENSE](https://github.com/etcd-io/jetcd/blob/master/LICENSE) file for details.
|
||||
|
||||
|
||||
|
|
55
build.gradle
55
build.gradle
|
@ -19,13 +19,13 @@ buildscript {
|
|||
gradlePluginPortal()
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.github.ben-manes:gradle-versions-plugin:${libs.versions.versionsPlugin.get()}"
|
||||
classpath "com.diffplug.spotless:spotless-plugin-gradle:${libs.versions.spotlessPlugin.get()}"
|
||||
classpath "net.ltgt.gradle:gradle-errorprone-plugin:${libs.versions.errorPronePlugin.get()}"
|
||||
classpath "io.github.gradle-nexus:publish-plugin:${libs.versions.nexusPublishPlugin.get()}"
|
||||
classpath "com.adarshr:gradle-test-logger-plugin:${libs.versions.testLoggerPlugin.get()}"
|
||||
classpath "pl.allegro.tech.build:axion-release-plugin:${libs.versions.axionReleasePlugin.get()}"
|
||||
classpath "org.gradle:test-retry-gradle-plugin:${libs.versions.testRetryPlugin.get()}"
|
||||
classpath "com.github.ben-manes:gradle-versions-plugin:$versionsPluginVersion"
|
||||
classpath "com.diffplug.spotless:spotless-plugin-gradle:$spotlessPluginVersion"
|
||||
classpath "net.ltgt.gradle:gradle-errorprone-plugin:$errorPronePluginVersion"
|
||||
classpath "io.github.gradle-nexus:publish-plugin:$nexusPublishPluginVersion"
|
||||
classpath "com.adarshr:gradle-test-logger-plugin:$testLoggerPluginVersion"
|
||||
classpath "se.ascp.gradle:gradle-versions-filter:$versionsFilterPluginVersion"
|
||||
classpath "pl.allegro.tech.build:axion-release-plugin:$axionReleasePluginVersion"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,52 +49,25 @@ subprojects {
|
|||
apply from: "${rootProject.projectDir}/gradle/publishing-release-tasks.gradle"
|
||||
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'com.adarshr.test-logger'
|
||||
apply plugin: 'org.gradle.test-retry'
|
||||
apply plugin: "com.adarshr.test-logger"
|
||||
|
||||
configurations.api {
|
||||
exclude group: 'com.google.code.gson', module: 'gson'
|
||||
exclude group: 'com.google.android', module: 'annotations'
|
||||
exclude group: 'com.google.j2objc', module: 'j2objc-annotations'
|
||||
exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations'
|
||||
exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
|
||||
exclude group: 'com.google.code.findbugs', module: 'jsr305'
|
||||
exclude group: 'org.checkerframework', module: 'checker-compat-qual'
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.release = 11
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(11)
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
|
||||
maxParallelForks = Runtime.runtime.availableProcessors() ?: 1
|
||||
|
||||
retry {
|
||||
maxRetries = 1
|
||||
maxFailures = 5
|
||||
}
|
||||
}
|
||||
testing {
|
||||
suites {
|
||||
test {
|
||||
useJUnitJupiter()
|
||||
}
|
||||
}
|
||||
maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
|
||||
}
|
||||
|
||||
testlogger {
|
||||
theme 'mocha-parallel'
|
||||
showStandardStreams false
|
||||
}
|
||||
|
||||
tasks.register('allDeps', DependencyReportTask)
|
||||
|
||||
tasks.withType(AbstractArchiveTask).configureEach {
|
||||
preserveFileTimestamps = false
|
||||
reproducibleFileOrder = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,61 @@
|
|||
## etcd Community Code of Conduct
|
||||
## CoreOS Community Code of Conduct
|
||||
|
||||
etcd follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
### Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of
|
||||
fostering an open and welcoming community, we pledge to respect all people who
|
||||
contribute through reporting issues, posting feature requests, updating
|
||||
documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free
|
||||
experience for everyone, regardless of level of experience, gender, gender
|
||||
identity and expression, sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as physical or electronic addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct. By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently applying these
|
||||
principles to every aspect of managing this project. Project maintainers who do
|
||||
not follow or enforce the Code of Conduct may be permanently removed from the
|
||||
project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting a project maintainer, Brandon Philips
|
||||
<brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant
|
||||
(http://contributor-covenant.org), version 1.2.0, available at
|
||||
http://contributor-covenant.org/version/1/2/0/
|
||||
|
||||
### CoreOS Events Code of Conduct
|
||||
|
||||
CoreOS events are working conferences intended for professional networking and
|
||||
collaboration in the CoreOS community. Attendees are expected to behave
|
||||
according to professional standards and in accordance with their employer’s
|
||||
policies on appropriate workplace behavior.
|
||||
|
||||
While at CoreOS events or related social networking opportunities, attendees
|
||||
should not engage in discriminatory or offensive speech or actions including
|
||||
but not limited to gender, sexuality, race, age, disability, or religion.
|
||||
Speakers should be especially aware of these concerns.
|
||||
|
||||
CoreOS does not condone any statements by speakers contrary to these standards.
|
||||
CoreOS reserves the right to deny entrance and/or eject from an event (without
|
||||
refund) any individual found to be engaging in discriminatory or offensive
|
||||
speech or actions.
|
||||
|
||||
Please bring any concerns to the immediate attention of designated on-site
|
||||
staff, Brandon Philips <brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
|
||||
|
|
|
@ -26,3 +26,6 @@ for (KeyValue kv : response.getKvs()) {
|
|||
);
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ The Lease interface provides methods to grant, revoke, and keepalive leases.
|
|||
1. This function is called periodically by `keepAliveSchedule`.
|
||||
2. The lease may expire as:
|
||||
* The etcd respond overtime.
|
||||
* The client fails to send request in time.
|
||||
* The client faile to send request in time.
|
||||
3. It will scan the keepAlives map and find the leases that requires keepAlive requests to send based on its nextKeepAliveTime.
|
||||
4. Send request to the StreamObserver for these leases.
|
||||
|
||||
|
@ -66,4 +66,4 @@ The Lease interface provides methods to grant, revoke, and keepalive leases.
|
|||
|
||||
1. The StreamObserver is the stream etcd uses to send responses.
|
||||
2. The gRPC client calls onNext when etcd server delivers a response.
|
||||
3. The onNext function will call `processKeepAliveRespond`.
|
||||
3. The onNext function will call `processKeepAliveRespond`.
|
|
@ -1,21 +0,0 @@
|
|||
= Release
|
||||
|
||||
+
|
||||
[source,shell]
|
||||
----
|
||||
$ ./gradlew currentVersion
|
||||
Project version: 0.7.5-SNAPSHOT
|
||||
|
||||
$ ./gradlew release
|
||||
|
||||
$ git tag
|
||||
jetcd-0.7.5
|
||||
|
||||
$ ./gradlew currentVersion
|
||||
Project version: 0.7.5
|
||||
|
||||
$ ./gradlew publish
|
||||
published jetcd-0.7.5 release version
|
||||
|
||||
$ ./gradlew markNextVersion -Prelease.version=0.7.6
|
||||
----
|
|
@ -1,36 +0,0 @@
|
|||
# How to Build Jectd Client for One TLS Secured Etcd Cluster
|
||||
|
||||
# prepare certification files
|
||||
|
||||
If your etcd cluster is installed using [etcdadm](https://github.com/kubernetes-sigs/etcdadm), you are likely to find
|
||||
certification files in the path `/etc/etcd/pki/`: `ca.crt`, `etcdctl-etcd-client.key`, `etcdctl-etcd-client.crt`.
|
||||
|
||||
If your etcd cluster is the builtin etcd in one kubernetes cluster(
|
||||
using [kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/setup-ha-etcd-with-kubeadm/)),
|
||||
|
||||
you can find the same files in `/etc/kubernetes/pki/etcd/`: `ca.crt`, `healthcheck-client.crt`, `healthcheck-client.key`.
|
||||
|
||||
Because `SslContextBuilder` only support a PKCS#8 private key file in PEM format, convert `etcdctl-etcd-client.key`
|
||||
to `etcdctl-etcd-client.key.pem` according
|
||||
to [netty SslContextBuilder doc](https://netty.io/wiki/sslcontextbuilder-and-private-key.html).
|
||||
|
||||
# build jetcd client
|
||||
|
||||
```
|
||||
File cert = new File("ca.crt");
|
||||
File keyCertChainFile = new File("etcdctl-etcd-client.crt");
|
||||
File keyFile = new File("etcdctl-etcd-client.key.pem");
|
||||
SslContext context = GrpcSslContexts.forClient()
|
||||
.trustManager(cert)
|
||||
.keyManager(keyCertChainFile, keyFile)
|
||||
.build();
|
||||
Client client = Client.builder()
|
||||
.endpoints("https://10.168.168.66:2379")
|
||||
.sslContext(context)
|
||||
.build();
|
||||
|
||||
client.getClusterClient().listMember().get().getMembers().forEach(member -> {
|
||||
logger.info("member: {}", member);
|
||||
});
|
||||
|
||||
```
|
|
@ -12,15 +12,15 @@ The Watch provide methods to watch on a key interval and cancel a watcher. If th
|
|||
|
||||
4. Cancel watch request, the etcd client should process watch cancellation and filter all the notification after cancellation request.
|
||||
|
||||
5. The watch client should be able to make a progress notify request that propagates the latest revision number to all watches.
|
||||
5. The watch client should be able to make a progress notify request that propagates the latest revision number to all watches
|
||||
|
||||
# Implementation
|
||||
|
||||
The etcd client process watch request with [watch function](#watch-function), process notification with [processEvents function](#processevents-function), process resume with [resume function](#resume-function), process cancel with [cancelWatch function](#cancelwatch-function) and request progress with [requestProgress function](#requestProgress-function).
|
||||
The etcd client process watch request with [watch function](#watch-function), process notification with [processEvents function](#processevents-function) , process resume with [resume function](#resume-function), process cancel with [cancelWatch function](#cancelwatch-function) and request progress with [requestProgress function](#requestProgress-function).
|
||||
|
||||
## watch function
|
||||
|
||||
Watch watches on a key interval.
|
||||
Watch watches on a key interval.
|
||||
|
||||
1. Send create request to [requestStream](#requeststream-instance).
|
||||
2. If the watch is create successfully, the `onCreate` will be called and the ListenableFuture task will be completed.
|
||||
|
@ -116,4 +116,4 @@ Process cancel response from etcd server.
|
|||
Resume all the the watchers on new requestStream.
|
||||
|
||||
1. Build new watch creation request for old watcher with last revision + 1.
|
||||
2. Call `watch` function with the new watch creation request.
|
||||
2. Call `watch` function with the new watch creation request.
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright 2016-2023 The jetcd authors
|
||||
Copyright 2016-2021 The jetcd authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -271,3 +271,4 @@
|
|||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="CHECKSTYLE:ON"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
|
|
|
@ -10,4 +10,4 @@ Unless required by applicable law or agreed to in writing, software
|
|||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
limitations under the License.
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright 2016-2023 The jetcd authors
|
||||
Copyright 2016-2021 The jetcd authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -23,8 +23,6 @@
|
|||
|
||||
<description>PMD Rules that govern static code analysis for jetcd</description>
|
||||
|
||||
<exclude-pattern>.*/io/etcd/jetcd/api/.*</exclude-pattern>
|
||||
|
||||
<rule ref="category/java/bestpractices.xml/AvoidUsingHardCodedIP"/>
|
||||
<rule ref="category/java/bestpractices.xml/UnusedFormalParameter"/>
|
||||
<rule ref="category/java/bestpractices.xml/UnusedLocalVariable"/>
|
||||
|
@ -44,7 +42,6 @@
|
|||
<rule ref="category/java/codestyle.xml/UselessQualifiedThis"/>
|
||||
<rule ref="category/java/codestyle.xml/ControlStatementBraces"/>
|
||||
<rule ref="category/java/codestyle.xml/UnnecessaryImport"/>
|
||||
<rule ref="category/java/codestyle.xml/EmptyControlStatement"/>
|
||||
|
||||
<rule ref="category/java/design.xml/FinalFieldCouldBeStatic">
|
||||
<priority>4</priority>
|
||||
|
@ -74,6 +71,15 @@
|
|||
</properties>
|
||||
</rule>
|
||||
|
||||
<rule ref="category/java/errorprone.xml/EmptyFinallyBlock"/>
|
||||
<rule ref="category/java/errorprone.xml/EmptyIfStmt"/>
|
||||
<rule ref="category/java/errorprone.xml/EmptyInitializer"/>
|
||||
<rule ref="category/java/errorprone.xml/EmptyStatementBlock"/>
|
||||
<rule ref="category/java/errorprone.xml/EmptyStatementNotInLoop"/>
|
||||
<rule ref="category/java/errorprone.xml/EmptySwitchStatements"/>
|
||||
<rule ref="category/java/errorprone.xml/EmptySynchronizedBlock"/>
|
||||
<rule ref="category/java/errorprone.xml/EmptyTryBlock"/>
|
||||
<rule ref="category/java/errorprone.xml/EmptyWhileStmt"/>
|
||||
<rule ref="category/java/errorprone.xml/JumbledIncrementer"/>
|
||||
<rule ref="category/java/errorprone.xml/MisplacedNullCheck"/>
|
||||
<rule ref="category/java/errorprone.xml/OverrideBothEqualsAndHashcode"/>
|
||||
|
@ -90,4 +96,4 @@
|
|||
|
||||
<rule ref="category/java/performance.xml/BigIntegerInstantiation"/>
|
||||
<rule ref="category/java/bestpractices.xml/PrimitiveWrapperInstantiation" />
|
||||
</ruleset>
|
||||
</ruleset>
|
|
@ -1,10 +1,30 @@
|
|||
#
|
||||
# project
|
||||
#
|
||||
gitProject = https://github.com/etcd-io/jetcd
|
||||
gitURL = git@github.com/etcd-io/jetcd.git
|
||||
gitProject = 'https://github.com/etcd-io/jetcd'
|
||||
gitURL = 'git@github.com/etcd-io/jetcd.git'
|
||||
|
||||
#
|
||||
# dependencies
|
||||
#
|
||||
grpcVersion = 1.42.1
|
||||
protocVersion = 3.17.2
|
||||
|
||||
#
|
||||
# plugins
|
||||
#
|
||||
versionsPluginVersion = 0.39.0
|
||||
versionsFilterPluginVersion = 0.1.10
|
||||
errorPronePluginVersion = 2.0.2
|
||||
spotlessPluginVersion = 6.0.0
|
||||
shadowPluginVersion = 7.1.0
|
||||
testLoggerPluginVersion = 3.1.0
|
||||
protobufPluginVersion = 0.8.18
|
||||
nexusPublishPluginVersion = 1.1.0
|
||||
axionReleasePluginVersion = 1.13.6
|
||||
|
||||
#
|
||||
# gradle
|
||||
#
|
||||
org.gradle.parallel = true
|
||||
|
||||
|
|
|
@ -1,46 +1,38 @@
|
|||
[versions]
|
||||
grpc = "1.74.0"
|
||||
log4j = "2.25.1"
|
||||
mockito = "5.18.0"
|
||||
slf4j = "2.0.17"
|
||||
guava = "33.4.8-jre"
|
||||
assertj = "3.27.3"
|
||||
junit = "5.13.4"
|
||||
testcontainers = "1.21.3"
|
||||
protoc = "3.25.1"
|
||||
failsafe = "3.3.2"
|
||||
awaitility = "4.3.0"
|
||||
commonsIo = "2.20.0"
|
||||
commonCompress = "1.28.0"
|
||||
autoService = "1.1.1"
|
||||
errorprone = "2.30.0"
|
||||
vertx = "5.0.1"
|
||||
picocli = "4.7.7"
|
||||
restAssured = "5.5.5"
|
||||
grpc = "1.42.1"
|
||||
log4j = "2.14.1"
|
||||
mockito = "4.0.0"
|
||||
slf4j = "1.7.32"
|
||||
guava = "31.0.1-jre"
|
||||
javaxAnnotation = "1.3.2"
|
||||
|
||||
versionsPlugin = "0.52.0"
|
||||
errorPronePlugin = "4.0.1"
|
||||
spotlessPlugin = "6.25.0"
|
||||
shadowPlugin = "8.1.1"
|
||||
testLoggerPlugin = "4.0.0"
|
||||
protobufPlugin = "0.9.5"
|
||||
nexusPublishPlugin = "2.0.0"
|
||||
axionReleasePlugin = "1.18.17"
|
||||
testRetryPlugin = "1.6.2"
|
||||
spotbugs = "4.5.0"
|
||||
assertj = "3.21.0"
|
||||
junit = "5.8.1"
|
||||
testcontainers = "1.16.2"
|
||||
protoc = "3.19.1"
|
||||
failsafe = "2.4.4"
|
||||
awaitility = "4.1.1"
|
||||
commonsIo = "2.11.0"
|
||||
commonCompress = "1.21"
|
||||
autoService = "1.0.1"
|
||||
jool = "0.9.14"
|
||||
jcommander = "1.81"
|
||||
errorprone = "2.10.0"
|
||||
|
||||
|
||||
[libraries]
|
||||
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||
javaxAnnotation = { module = "javax.annotation:javax.annotation-api", version.ref = "javaxAnnotation" }
|
||||
spotbugs = { module = "com.github.spotbugs:spotbugs-annotations", version.ref = "spotbugs" }
|
||||
assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" }
|
||||
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
||||
testcontainers = { module = "org.testcontainers:testcontainers", version.ref = "testcontainers" }
|
||||
protoc = { module = "com.google.protobuf:protoc", version.ref = "protoc" }
|
||||
failsafe = { module = "dev.failsafe:failsafe", version.ref = "failsafe" }
|
||||
failsafe = { module = "net.jodah:failsafe", version.ref = "failsafe" }
|
||||
awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" }
|
||||
picocli = { module = "info.picocli:picocli", version.ref = "picocli"}
|
||||
restAssured = { module = "io.rest-assured:rest-assured", version.ref = "restAssured"}
|
||||
jcommander = { module = "com.beust:jcommander", version.ref = "jcommander"}
|
||||
jool = { module = "org.jooq:jool-java-8", version.ref = "jool"}
|
||||
|
||||
commonsIo = { module = "commons-io:commons-io", version.ref = "commonsIo" }
|
||||
commonsCompress = { module = "org.apache.commons:commons-compress", version.ref = "commonCompress" }
|
||||
|
@ -53,34 +45,23 @@ grpcNetty = { module = "io.grpc:grpc-netty", version.ref = "grpc" }
|
|||
grpcProtobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpc" }
|
||||
grpcStub = { module = "io.grpc:grpc-stub", version.ref = "grpc" }
|
||||
grpcGrpclb = { module = "io.grpc:grpc-grpclb", version.ref = "grpc" }
|
||||
grpcInprocess = { module = "io.grpc:grpc-inprocess", version.ref = "grpc" }
|
||||
grpcUtil = { module = "io.grpc:grpc-util", version.ref = "grpc" }
|
||||
|
||||
vertxGrpc = { module = "io.vertx:vertx-grpc", version.ref = "vertx" }
|
||||
|
||||
log4jApi = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" }
|
||||
log4jCore = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
|
||||
log4jSlf4j = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.ref = "log4j" }
|
||||
log4jSlf4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" }
|
||||
log4j12 = { module = "org.apache.logging.log4j:log4j-1.2-api", version.ref = "log4j" }
|
||||
|
||||
autoServiceAnnotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoService"}
|
||||
autoServiceProcessor = { module = "com.google.auto.service:auto-service", version.ref = "autoService"}
|
||||
|
||||
javaxAnnotation = { module = "javax.annotation:javax.annotation-api", version.ref = "javaxAnnotation" }
|
||||
|
||||
errorprone = { module = "com.google.errorprone:error_prone_core", version.ref = "errorprone" }
|
||||
errorproneAnnotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "errorprone" }
|
||||
errorproneJavac = { module = "com.google.errorprone:javac", version = "9+" }
|
||||
errorproneJavac = { module = "com.google.errorprone:javac", version = "9+181-r4173-1" }
|
||||
|
||||
[bundles]
|
||||
grpc = [ "grpcCore", "grpcNetty", "grpcProtobuf", "grpcStub", "grpcGrpclb", "grpcUtil"]
|
||||
grpcTest = [ "grpcInprocess"]
|
||||
log4j = [ "log4jApi", "log4jCore", "log4jSlf4j", "log4j12" ]
|
||||
mockito = [ "mockitoCore", "mockitoJunit" ]
|
||||
testing = ["junit", "assertj", "mockitoCore", "mockitoJunit"]
|
||||
javax = [ "javaxAnnotation" ]
|
||||
grpc = [ "grpcCore", "grpcNetty", "grpcProtobuf", "grpcStub", "grpcGrpclb" ]
|
||||
log4j = [ "log4jApi", "log4jCore", "log4jSlf4j", "log4j12" ]
|
||||
mockito = [ "mockitoCore", "mockitoJunit" ]
|
||||
testing = ["junit", "assertj", "mockitoCore", "mockitoJunit"]
|
||||
|
||||
|
||||
[plugins]
|
||||
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadowPlugin" }
|
||||
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }
|
||||
|
|
|
@ -20,10 +20,10 @@ apply plugin: "io.github.gradle-nexus.publish-plugin"
|
|||
|
||||
ext {
|
||||
if (!project.hasProperty('nexusUsername')) {
|
||||
nexusUsername = "$System.env.NEXUS_USERNAME"
|
||||
nexusUsername = "$System.env.OSSRH_USERNAME"
|
||||
}
|
||||
if (!project.hasProperty('nexusPassword')) {
|
||||
nexusPassword = "$System.env.NEXUS_PASSWORD"
|
||||
nexusPassword = "$System.env.OSSRH_PASSWORD"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016-2023 The jetcd authors
|
||||
* Copyright 2016-2020 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -22,4 +22,4 @@ task pubblications {
|
|||
println "Publication $publication.name [$publication.groupId/$publication.artifactId/$publication.version]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016-2023 The jetcd authors
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -89,12 +89,9 @@ signing {
|
|||
sign publishing.publications."${project.name}"
|
||||
}
|
||||
|
||||
check.dependsOn javadoc
|
||||
|
||||
javadoc {
|
||||
if(JavaVersion.current().isJava9Compatible()) {
|
||||
options.addBooleanOption('html5', true)
|
||||
}
|
||||
|
||||
exclude "**/io/etcd/jetcd/api/**"
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ apply plugin: "pmd"
|
|||
|
||||
dependencies {
|
||||
errorprone(libs.errorprone)
|
||||
errorprone(libs.errorproneAnnotations)
|
||||
errorproneJavac(libs.errorproneJavac)
|
||||
}
|
||||
|
||||
|
@ -28,10 +27,9 @@ tasks.withType(JavaCompile).configureEach {
|
|||
excludedPaths = '.*/generated/.*'
|
||||
disableWarningsInGeneratedCode = true
|
||||
}
|
||||
options.deprecation = true
|
||||
}
|
||||
|
||||
pmd {
|
||||
consoleOutput = true
|
||||
ruleSets = [ "${project.rootDir}/etc/pmd-ruleset.xml" ]
|
||||
ruleSets = [ rootProject.file("etc/pmd-ruleset.xml") ]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016-2023 The jetcd authors
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -25,4 +25,4 @@ scmVersion {
|
|||
|
||||
allprojects {
|
||||
project.version = scmVersion.version
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ spotless {
|
|||
endWithNewline()
|
||||
importOrderFile(rootProject.file('etc/eclipse.importorder'))
|
||||
eclipse().configFile(rootProject.file('etc/eclipse-formatter-config.xml'))
|
||||
targetExclude("build/generated/**/*.java")
|
||||
//licenseHeaderFile(rootProject.file('etc/license.txt'))
|
||||
//licenseHeader 'Copyright $YEAR The jetcd authors'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,16 +15,11 @@
|
|||
*/
|
||||
|
||||
apply plugin: 'com.github.ben-manes.versions'
|
||||
apply plugin: 'se.ascp.gradle.gradle-versions-filter'
|
||||
|
||||
dependencyUpdates.configure {
|
||||
|
||||
def isNonStable = { String version ->
|
||||
return ['alpha', 'beta', 'rc', 'cr', 'm', 'ea'].any { keyword -> version.toUpperCase().contains(keyword.toUpperCase())}
|
||||
}
|
||||
|
||||
rejectVersionIf {
|
||||
isNonStable(it.candidate.version)
|
||||
}
|
||||
versionsFilter {
|
||||
exclusiveQualifiers = ['alpha', 'beta', 'rc', 'cr', 'm', 'ea']
|
||||
log = false
|
||||
}
|
||||
|
||||
task deps {
|
||||
|
|
Binary file not shown.
|
@ -1,7 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
|
@ -57,7 +55,7 @@
|
|||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
@ -82,11 +80,13 @@ do
|
|||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
@ -114,7 +114,7 @@ case "$( uname )" in #(
|
|||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
|
@ -133,29 +133,22 @@ location of your Java installation."
|
|||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
@ -200,28 +193,18 @@ if "$cygwin" || "$msys" ; then
|
|||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
|
|
@ -1,94 +1,89 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
|
|
@ -17,4 +17,6 @@
|
|||
|
||||
dependencies {
|
||||
api libs.slf4j
|
||||
api libs.guava
|
||||
api libs.grpcCore
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.common;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public abstract class Service implements AutoCloseable {
|
||||
private final AtomicBoolean running;
|
||||
|
||||
protected Service() {
|
||||
this.running = new AtomicBoolean();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (this.running.compareAndSet(false, true)) {
|
||||
doStart();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (this.running.compareAndSet(true, false)) {
|
||||
doStop();
|
||||
}
|
||||
}
|
||||
|
||||
public void restart() {
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
stop();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running.get();
|
||||
}
|
||||
|
||||
protected abstract void doStart();
|
||||
|
||||
protected abstract void doStop();
|
||||
}
|
|
@ -31,22 +31,12 @@ import io.grpc.Status;
|
|||
*/
|
||||
public enum ErrorCode {
|
||||
|
||||
CANCELLED(Status.CANCELLED),
|
||||
UNKNOWN(Status.UNKNOWN),
|
||||
INVALID_ARGUMENT(Status.INVALID_ARGUMENT),
|
||||
DEADLINE_EXCEEDED(Status.DEADLINE_EXCEEDED),
|
||||
NOT_FOUND(Status.NOT_FOUND),
|
||||
ALREADY_EXISTS(Status.ALREADY_EXISTS),
|
||||
PERMISSION_DENIED(Status.PERMISSION_DENIED),
|
||||
UNAUTHENTICATED(Status.UNAUTHENTICATED),
|
||||
RESOURCE_EXHAUSTED(Status.RESOURCE_EXHAUSTED),
|
||||
FAILED_PRECONDITION(Status.FAILED_PRECONDITION),
|
||||
ABORTED(Status.ABORTED),
|
||||
OUT_OF_RANGE(Status.OUT_OF_RANGE),
|
||||
UNIMPLEMENTED(Status.UNIMPLEMENTED),
|
||||
INTERNAL(Status.INTERNAL),
|
||||
UNAVAILABLE(Status.UNAVAILABLE),
|
||||
DATA_LOSS(Status.DATA_LOSS),;
|
||||
CANCELLED(Status.CANCELLED), UNKNOWN(Status.UNKNOWN), INVALID_ARGUMENT(Status.INVALID_ARGUMENT),
|
||||
DEADLINE_EXCEEDED(Status.DEADLINE_EXCEEDED), NOT_FOUND(Status.NOT_FOUND), ALREADY_EXISTS(Status.ALREADY_EXISTS),
|
||||
PERMISSION_DENIED(Status.PERMISSION_DENIED), UNAUTHENTICATED(Status.UNAUTHENTICATED),
|
||||
RESOURCE_EXHAUSTED(Status.RESOURCE_EXHAUSTED), FAILED_PRECONDITION(Status.FAILED_PRECONDITION), ABORTED(Status.ABORTED),
|
||||
OUT_OF_RANGE(Status.OUT_OF_RANGE), UNIMPLEMENTED(Status.UNIMPLEMENTED), INTERNAL(Status.INTERNAL),
|
||||
UNAVAILABLE(Status.UNAVAILABLE), DATA_LOSS(Status.DATA_LOSS),;
|
||||
|
||||
private static final Map<Integer, ErrorCode> errorByRpcCode;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.etcd.jetcd.common.exception;
|
||||
|
||||
import java.util.Objects;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Base exception type for all exceptions produced by the etcd service.
|
||||
|
@ -27,7 +27,7 @@ public class EtcdException extends RuntimeException {
|
|||
|
||||
EtcdException(ErrorCode code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = Objects.requireNonNull(code, "Error code must not be null");
|
||||
this.code = checkNotNull(code);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,10 +16,9 @@
|
|||
|
||||
package io.etcd.jetcd.common.exception;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import io.grpc.Status;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static io.grpc.Status.fromThrowable;
|
||||
|
||||
/**
|
||||
|
@ -66,7 +65,7 @@ public final class EtcdExceptionFactory {
|
|||
}
|
||||
|
||||
public static EtcdException toEtcdException(Throwable cause) {
|
||||
Objects.requireNonNull(cause, "cause can't be null");
|
||||
checkNotNull(cause, "cause can't be null");
|
||||
if (cause instanceof EtcdException) {
|
||||
return (EtcdException) cause;
|
||||
}
|
||||
|
@ -75,7 +74,7 @@ public final class EtcdExceptionFactory {
|
|||
}
|
||||
|
||||
public static EtcdException toEtcdException(Status status) {
|
||||
Objects.requireNonNull(status, "status can't be null");
|
||||
checkNotNull(status, "status can't be null");
|
||||
return fromStatus(status);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,17 +21,17 @@ dependencies {
|
|||
|
||||
api libs.slf4j
|
||||
api libs.guava
|
||||
api libs.javaxAnnotation
|
||||
api libs.bundles.grpc
|
||||
api libs.spotbugs
|
||||
api libs.failsafe
|
||||
|
||||
//compileOnly libs.javaxAnnotation
|
||||
compileOnly libs.autoServiceAnnotations
|
||||
|
||||
api libs.autoServiceAnnotations
|
||||
annotationProcessor libs.autoServiceProcessor
|
||||
|
||||
testImplementation project(':jetcd-launcher')
|
||||
testImplementation project(':jetcd-test')
|
||||
|
||||
testImplementation libs.restAssured
|
||||
testImplementation libs.awaitility
|
||||
testImplementation libs.commonsIo
|
||||
testImplementation libs.bundles.testing
|
||||
|
|
|
@ -14,9 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.impl;
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import io.etcd.jetcd.Response;
|
||||
import io.etcd.jetcd.api.ResponseHeader;
|
||||
|
||||
public class AbstractResponse<R> implements Response {
|
|
@ -14,14 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.impl;
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.api.AuthDisableRequest;
|
||||
import io.etcd.jetcd.api.AuthEnableRequest;
|
||||
import io.etcd.jetcd.api.AuthGrpc;
|
||||
import io.etcd.jetcd.api.AuthRoleAddRequest;
|
||||
import io.etcd.jetcd.api.AuthRoleDeleteRequest;
|
||||
import io.etcd.jetcd.api.AuthRoleGetRequest;
|
||||
|
@ -35,7 +34,6 @@ import io.etcd.jetcd.api.AuthUserGetRequest;
|
|||
import io.etcd.jetcd.api.AuthUserGrantRoleRequest;
|
||||
import io.etcd.jetcd.api.AuthUserListRequest;
|
||||
import io.etcd.jetcd.api.AuthUserRevokeRoleRequest;
|
||||
import io.etcd.jetcd.api.VertxAuthGrpc;
|
||||
import io.etcd.jetcd.auth.AuthDisableResponse;
|
||||
import io.etcd.jetcd.auth.AuthEnableResponse;
|
||||
import io.etcd.jetcd.auth.AuthRoleAddResponse;
|
||||
|
@ -53,153 +51,120 @@ import io.etcd.jetcd.auth.AuthUserListResponse;
|
|||
import io.etcd.jetcd.auth.AuthUserRevokeRoleResponse;
|
||||
import io.etcd.jetcd.auth.Permission;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Implementation of etcd auth client.
|
||||
*/
|
||||
final class AuthImpl extends Impl implements Auth {
|
||||
final class AuthImpl implements Auth {
|
||||
|
||||
private final VertxAuthGrpc.AuthVertxStub stub;
|
||||
private final AuthGrpc.AuthFutureStub stub;
|
||||
private final ClientConnectionManager connectionManager;
|
||||
|
||||
AuthImpl(ClientConnectionManager connectionManager) {
|
||||
super(connectionManager);
|
||||
|
||||
this.stub = connectionManager.newStub(VertxAuthGrpc::newVertxStub);
|
||||
this.connectionManager = connectionManager;
|
||||
this.stub = connectionManager.newStub(AuthGrpc::newFutureStub);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthEnableResponse> authEnable() {
|
||||
AuthEnableRequest enableRequest = AuthEnableRequest.getDefaultInstance();
|
||||
return completable(
|
||||
this.stub.authEnable(enableRequest),
|
||||
AuthEnableResponse::new);
|
||||
return Util.toCompletableFuture(this.stub.authEnable(enableRequest), AuthEnableResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthDisableResponse> authDisable() {
|
||||
AuthDisableRequest disableRequest = AuthDisableRequest.getDefaultInstance();
|
||||
return completable(
|
||||
this.stub.authDisable(disableRequest),
|
||||
AuthDisableResponse::new);
|
||||
return Util.toCompletableFuture(this.stub.authDisable(disableRequest), AuthDisableResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthUserAddResponse> userAdd(ByteSequence user, ByteSequence password) {
|
||||
requireNonNull(user, "user can't be null");
|
||||
requireNonNull(password, "password can't be null");
|
||||
checkNotNull(user, "user can't be null");
|
||||
checkNotNull(password, "password can't be null");
|
||||
|
||||
AuthUserAddRequest addRequest = AuthUserAddRequest.newBuilder()
|
||||
.setNameBytes(ByteString.copyFrom(user.getBytes()))
|
||||
.setPasswordBytes(ByteString.copyFrom(password.getBytes()))
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.userAdd(addRequest),
|
||||
AuthUserAddResponse::new);
|
||||
AuthUserAddRequest addRequest = AuthUserAddRequest.newBuilder().setNameBytes(user.getByteString())
|
||||
.setPasswordBytes(password.getByteString()).build();
|
||||
return Util.toCompletableFuture(this.stub.userAdd(addRequest), AuthUserAddResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthUserDeleteResponse> userDelete(ByteSequence user) {
|
||||
requireNonNull(user, "user can't be null");
|
||||
checkNotNull(user, "user can't be null");
|
||||
|
||||
AuthUserDeleteRequest deleteRequest = AuthUserDeleteRequest.newBuilder()
|
||||
.setNameBytes(ByteString.copyFrom(user.getBytes()))
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.userDelete(deleteRequest),
|
||||
AuthUserDeleteResponse::new);
|
||||
AuthUserDeleteRequest deleteRequest = AuthUserDeleteRequest.newBuilder().setNameBytes(user.getByteString()).build();
|
||||
return Util.toCompletableFuture(this.stub.userDelete(deleteRequest), AuthUserDeleteResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthUserChangePasswordResponse> userChangePassword(ByteSequence user, ByteSequence password) {
|
||||
requireNonNull(user, "user can't be null");
|
||||
requireNonNull(password, "password can't be null");
|
||||
checkNotNull(user, "user can't be null");
|
||||
checkNotNull(password, "password can't be null");
|
||||
|
||||
AuthUserChangePasswordRequest changePasswordRequest = AuthUserChangePasswordRequest.newBuilder()
|
||||
.setNameBytes(ByteString.copyFrom(user.getBytes()))
|
||||
.setPasswordBytes(ByteString.copyFrom(password.getBytes()))
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.userChangePassword(changePasswordRequest),
|
||||
AuthUserChangePasswordResponse::new);
|
||||
.setNameBytes(user.getByteString()).setPasswordBytes(password.getByteString()).build();
|
||||
return Util.toCompletableFuture(this.stub.userChangePassword(changePasswordRequest),
|
||||
AuthUserChangePasswordResponse::new, this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthUserGetResponse> userGet(ByteSequence user) {
|
||||
requireNonNull(user, "user can't be null");
|
||||
checkNotNull(user, "user can't be null");
|
||||
|
||||
AuthUserGetRequest userGetRequest = AuthUserGetRequest.newBuilder()
|
||||
.setNameBytes(ByteString.copyFrom(user.getBytes()))
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.userGet(userGetRequest),
|
||||
AuthUserGetResponse::new);
|
||||
AuthUserGetRequest userGetRequest = AuthUserGetRequest.newBuilder().setNameBytes(user.getByteString()).build();
|
||||
return Util.toCompletableFuture(this.stub.userGet(userGetRequest), AuthUserGetResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthUserListResponse> userList() {
|
||||
AuthUserListRequest userListRequest = AuthUserListRequest.getDefaultInstance();
|
||||
|
||||
return completable(
|
||||
this.stub.userList(userListRequest),
|
||||
AuthUserListResponse::new);
|
||||
return Util.toCompletableFuture(this.stub.userList(userListRequest), AuthUserListResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthUserGrantRoleResponse> userGrantRole(ByteSequence user, ByteSequence role) {
|
||||
requireNonNull(user, "user can't be null");
|
||||
requireNonNull(role, "key can't be null");
|
||||
checkNotNull(user, "user can't be null");
|
||||
checkNotNull(role, "key can't be null");
|
||||
|
||||
AuthUserGrantRoleRequest userGrantRoleRequest = AuthUserGrantRoleRequest.newBuilder()
|
||||
.setUserBytes(ByteString.copyFrom(user.getBytes()))
|
||||
.setRoleBytes(ByteString.copyFrom(role.getBytes()))
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.userGrantRole(userGrantRoleRequest),
|
||||
AuthUserGrantRoleResponse::new);
|
||||
AuthUserGrantRoleRequest userGrantRoleRequest = AuthUserGrantRoleRequest.newBuilder().setUserBytes(user.getByteString())
|
||||
.setRoleBytes(role.getByteString()).build();
|
||||
return Util.toCompletableFuture(this.stub.userGrantRole(userGrantRoleRequest), AuthUserGrantRoleResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthUserRevokeRoleResponse> userRevokeRole(ByteSequence user, ByteSequence role) {
|
||||
requireNonNull(user, "user can't be null");
|
||||
requireNonNull(role, "key can't be null");
|
||||
checkNotNull(user, "user can't be null");
|
||||
checkNotNull(role, "key can't be null");
|
||||
|
||||
AuthUserRevokeRoleRequest userRevokeRoleRequest = AuthUserRevokeRoleRequest.newBuilder()
|
||||
.setNameBytes(ByteString.copyFrom(user.getBytes()))
|
||||
.setRoleBytes(ByteString.copyFrom(role.getBytes()))
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.userRevokeRole(userRevokeRoleRequest),
|
||||
AuthUserRevokeRoleResponse::new);
|
||||
.setNameBytes(user.getByteString()).setRoleBytes(role.getByteString()).build();
|
||||
return Util.toCompletableFuture(this.stub.userRevokeRole(userRevokeRoleRequest), AuthUserRevokeRoleResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthRoleAddResponse> roleAdd(ByteSequence user) {
|
||||
requireNonNull(user, "user can't be null");
|
||||
checkNotNull(user, "user can't be null");
|
||||
|
||||
AuthRoleAddRequest roleAddRequest = AuthRoleAddRequest.newBuilder().setNameBytes(ByteString.copyFrom(user.getBytes()))
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.roleAdd(roleAddRequest),
|
||||
AuthRoleAddResponse::new);
|
||||
AuthRoleAddRequest roleAddRequest = AuthRoleAddRequest.newBuilder().setNameBytes(user.getByteString()).build();
|
||||
return Util.toCompletableFuture(this.stub.roleAdd(roleAddRequest), AuthRoleAddResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthRoleGrantPermissionResponse> roleGrantPermission(ByteSequence role, ByteSequence key,
|
||||
ByteSequence rangeEnd, Permission.Type permType) {
|
||||
requireNonNull(role, "role can't be null");
|
||||
requireNonNull(key, "key can't be null");
|
||||
requireNonNull(rangeEnd, "rangeEnd can't be null");
|
||||
requireNonNull(permType, "permType can't be null");
|
||||
checkNotNull(role, "role can't be null");
|
||||
checkNotNull(key, "key can't be null");
|
||||
checkNotNull(rangeEnd, "rangeEnd can't be null");
|
||||
checkNotNull(permType, "permType can't be null");
|
||||
|
||||
io.etcd.jetcd.api.Permission.Type type;
|
||||
switch (permType) {
|
||||
|
@ -218,70 +183,54 @@ final class AuthImpl extends Impl implements Auth {
|
|||
}
|
||||
|
||||
io.etcd.jetcd.api.Permission perm = io.etcd.jetcd.api.Permission.newBuilder()
|
||||
.setKey(ByteString.copyFrom(key.getBytes()))
|
||||
.setRangeEnd(ByteString.copyFrom(rangeEnd.getBytes()))
|
||||
.setKey(key.getByteString())
|
||||
.setRangeEnd(rangeEnd.getByteString())
|
||||
.setPermType(type)
|
||||
.build();
|
||||
|
||||
AuthRoleGrantPermissionRequest roleGrantPermissionRequest = AuthRoleGrantPermissionRequest.newBuilder()
|
||||
.setNameBytes(ByteString.copyFrom(role.getBytes()))
|
||||
.setNameBytes(role.getByteString())
|
||||
.setPerm(perm)
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.roleGrantPermission(roleGrantPermissionRequest),
|
||||
AuthRoleGrantPermissionResponse::new);
|
||||
return Util.toCompletableFuture(this.stub.roleGrantPermission(roleGrantPermissionRequest),
|
||||
AuthRoleGrantPermissionResponse::new, this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthRoleGetResponse> roleGet(ByteSequence role) {
|
||||
requireNonNull(role, "role can't be null");
|
||||
checkNotNull(role, "role can't be null");
|
||||
|
||||
AuthRoleGetRequest roleGetRequest = AuthRoleGetRequest.newBuilder()
|
||||
.setRoleBytes(ByteString.copyFrom(role.getBytes()))
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.roleGet(roleGetRequest),
|
||||
AuthRoleGetResponse::new);
|
||||
AuthRoleGetRequest roleGetRequest = AuthRoleGetRequest.newBuilder().setRoleBytes(role.getByteString()).build();
|
||||
return Util.toCompletableFuture(this.stub.roleGet(roleGetRequest), AuthRoleGetResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthRoleListResponse> roleList() {
|
||||
AuthRoleListRequest roleListRequest = AuthRoleListRequest.getDefaultInstance();
|
||||
|
||||
return completable(
|
||||
this.stub.roleList(roleListRequest),
|
||||
AuthRoleListResponse::new);
|
||||
return Util.toCompletableFuture(this.stub.roleList(roleListRequest), AuthRoleListResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthRoleRevokePermissionResponse> roleRevokePermission(ByteSequence role, ByteSequence key,
|
||||
ByteSequence rangeEnd) {
|
||||
requireNonNull(role, "role can't be null");
|
||||
requireNonNull(key, "key can't be null");
|
||||
requireNonNull(rangeEnd, "rangeEnd can't be null");
|
||||
checkNotNull(role, "role can't be null");
|
||||
checkNotNull(key, "key can't be null");
|
||||
checkNotNull(rangeEnd, "rangeEnd can't be null");
|
||||
|
||||
AuthRoleRevokePermissionRequest roleRevokePermissionRequest = AuthRoleRevokePermissionRequest.newBuilder()
|
||||
.setRoleBytes(ByteString.copyFrom(role.getBytes()))
|
||||
.setKey(ByteString.copyFrom(key.getBytes()))
|
||||
.setRangeEnd(ByteString.copyFrom(rangeEnd.getBytes()))
|
||||
.setRoleBytes(role.getByteString()).setKeyBytes(key.getByteString()).setRangeEndBytes(rangeEnd.getByteString())
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.roleRevokePermission(roleRevokePermissionRequest),
|
||||
AuthRoleRevokePermissionResponse::new);
|
||||
return Util.toCompletableFuture(this.stub.roleRevokePermission(roleRevokePermissionRequest),
|
||||
AuthRoleRevokePermissionResponse::new, this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AuthRoleDeleteResponse> roleDelete(ByteSequence role) {
|
||||
requireNonNull(role, "role can't be null");
|
||||
AuthRoleDeleteRequest roleDeleteRequest = AuthRoleDeleteRequest.newBuilder()
|
||||
.setRoleBytes(ByteString.copyFrom(role.getBytes()))
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
this.stub.roleDelete(roleDeleteRequest),
|
||||
AuthRoleDeleteResponse::new);
|
||||
checkNotNull(role, "role can't be null");
|
||||
AuthRoleDeleteRequest roleDeleteRequest = AuthRoleDeleteRequest.newBuilder().setRoleBytes(role.getByteString()).build();
|
||||
return Util.toCompletableFuture(this.stub.roleDelete(roleDeleteRequest), AuthRoleDeleteResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
}
|
|
@ -17,10 +17,11 @@
|
|||
package io.etcd.jetcd;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Etcd binary bytes, easy to convert between byte[], String and ByteString.
|
||||
*/
|
||||
|
@ -32,7 +33,7 @@ public final class ByteSequence {
|
|||
private final ByteString byteString;
|
||||
|
||||
private ByteSequence(ByteString byteString) {
|
||||
Objects.requireNonNull(byteString, "byteString should not be null");
|
||||
checkNotNull(byteString, "byteString should not be null");
|
||||
this.byteString = byteString;
|
||||
this.hashVal = byteString.hashCode();
|
||||
}
|
||||
|
@ -48,7 +49,9 @@ public final class ByteSequence {
|
|||
if (prefix == null) {
|
||||
return false;
|
||||
}
|
||||
return byteString.startsWith(prefix.byteString);
|
||||
ByteString baseByteString = this.getByteString();
|
||||
ByteString prefixByteString = prefix.getByteString();
|
||||
return baseByteString.startsWith(prefixByteString);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,19 +61,8 @@ public final class ByteSequence {
|
|||
* @return a new {@code ByteSequence} instance
|
||||
*/
|
||||
public ByteSequence concat(ByteSequence other) {
|
||||
Objects.requireNonNull(other, "other byteSequence should not be null");
|
||||
return new ByteSequence(this.byteString.concat(other.byteString));
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate the given {@code ByteSequence} to this one.
|
||||
*
|
||||
* @param other string to concatenate
|
||||
* @return a new {@code ByteSequence} instance
|
||||
*/
|
||||
public ByteSequence concat(ByteString other) {
|
||||
Objects.requireNonNull(other, "other byteSequence should not be null");
|
||||
return new ByteSequence(this.byteString.concat(other));
|
||||
checkNotNull(other, "other byteSequence should not be null");
|
||||
return new ByteSequence(this.byteString.concat(other.getByteString()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,6 +114,10 @@ public final class ByteSequence {
|
|||
return hashVal;
|
||||
}
|
||||
|
||||
ByteString getByteString() {
|
||||
return this.byteString;
|
||||
}
|
||||
|
||||
public String toString(Charset charset) {
|
||||
return byteString.toString(charset);
|
||||
}
|
||||
|
@ -155,22 +151,10 @@ public final class ByteSequence {
|
|||
return new ByteSequence(ByteString.copyFrom(bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new ByteSequence from a {@link ByteString}.
|
||||
*
|
||||
* @param source input {@link ByteString}
|
||||
* @return the ByteSequence
|
||||
*/
|
||||
public static ByteSequence from(ByteString source) {
|
||||
return new ByteSequence(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new ByteSequence from raw bytes.
|
||||
*
|
||||
* @param source input bytes
|
||||
* @return the ByteSequence
|
||||
*/
|
||||
public static ByteSequence from(byte[] source) {
|
||||
return new ByteSequence(ByteString.copyFrom(source));
|
||||
}
|
||||
|
|
|
@ -27,57 +27,41 @@ public interface Client extends AutoCloseable {
|
|||
|
||||
/**
|
||||
* Returns the {@link Auth} client.
|
||||
*
|
||||
* @return the client.
|
||||
*/
|
||||
Auth getAuthClient();
|
||||
|
||||
/**
|
||||
* Returns the {@link KV} client.
|
||||
*
|
||||
* @return the client.
|
||||
*/
|
||||
KV getKVClient();
|
||||
|
||||
/**
|
||||
* Returns the {@link Cluster} client.
|
||||
*
|
||||
* @return the client.
|
||||
*/
|
||||
Cluster getClusterClient();
|
||||
|
||||
/**
|
||||
* Returns the {@link Maintenance} client.
|
||||
*
|
||||
* @return the client.
|
||||
*/
|
||||
Maintenance getMaintenanceClient();
|
||||
|
||||
/**
|
||||
* Returns the {@link Lease} client.
|
||||
*
|
||||
* @return the client.
|
||||
*/
|
||||
Lease getLeaseClient();
|
||||
|
||||
/**
|
||||
* Returns the {@link Watch} client.
|
||||
*
|
||||
* @return the client.
|
||||
*/
|
||||
Watch getWatchClient();
|
||||
|
||||
/**
|
||||
* Returns the {@link Lock} client.
|
||||
*
|
||||
* @return the client.
|
||||
*/
|
||||
Lock getLockClient();
|
||||
|
||||
/**
|
||||
* Returns the {@link Election} client.
|
||||
*
|
||||
* @return the client.
|
||||
*/
|
||||
Election getElectionClient();
|
||||
|
||||
|
@ -86,8 +70,6 @@ public interface Client extends AutoCloseable {
|
|||
|
||||
/**
|
||||
* Returns a new {@link ClientBuilder}.
|
||||
*
|
||||
* @return the builder.
|
||||
*/
|
||||
static ClientBuilder builder() {
|
||||
return new ClientBuilder();
|
||||
|
|
|
@ -19,30 +19,37 @@ package io.etcd.jetcd;
|
|||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
import io.etcd.jetcd.common.exception.EtcdException;
|
||||
import io.etcd.jetcd.common.exception.EtcdExceptionFactory;
|
||||
import io.etcd.jetcd.impl.ClientImpl;
|
||||
import io.etcd.jetcd.resolver.IPNameResolver;
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.netty.GrpcSslContexts;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.vertx.core.Vertx;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Streams;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
/**
|
||||
* ClientBuilder knows how to create a Client instance.
|
||||
* ClientBuilder knows how to create an Client instance.
|
||||
*/
|
||||
public final class ClientBuilder implements Cloneable {
|
||||
|
||||
|
@ -61,7 +68,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
private ByteSequence namespace = ByteSequence.EMPTY;
|
||||
private long retryDelay = 500;
|
||||
private long retryMaxDelay = 2500;
|
||||
private int retryMaxAttempts = 2;
|
||||
private ChronoUnit retryChronoUnit = ChronoUnit.MILLIS;
|
||||
private Duration keepaliveTime = Duration.ofSeconds(30L);
|
||||
private Duration keepaliveTimeout = Duration.ofSeconds(10L);
|
||||
|
@ -69,7 +75,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
private Duration retryMaxDuration;
|
||||
private Duration connectTimeout;
|
||||
private boolean waitForReady = true;
|
||||
private Vertx vertx;
|
||||
|
||||
ClientBuilder() {
|
||||
}
|
||||
|
@ -91,7 +96,7 @@ public final class ClientBuilder implements Cloneable {
|
|||
* @throws NullPointerException if target is null or one of endpoint is null
|
||||
*/
|
||||
public ClientBuilder target(String target) {
|
||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(target), "target can't be null or empty");
|
||||
checkArgument(!Strings.isNullOrEmpty(target), "target can't be null or empty");
|
||||
|
||||
this.target = target;
|
||||
|
||||
|
@ -132,7 +137,7 @@ public final class ClientBuilder implements Cloneable {
|
|||
* @throws IllegalArgumentException if some endpoint is invalid
|
||||
*/
|
||||
public ClientBuilder endpoints(Iterable<URI> endpoints) {
|
||||
Objects.requireNonNull(endpoints, "endpoints can't be null");
|
||||
checkNotNull(endpoints, "endpoints can't be null");
|
||||
|
||||
endpoints.forEach(e -> {
|
||||
if (e.getHost() == null) {
|
||||
|
@ -140,8 +145,8 @@ public final class ClientBuilder implements Cloneable {
|
|||
}
|
||||
});
|
||||
|
||||
final String target = StreamSupport.stream(endpoints.spliterator(), false)
|
||||
.map(e -> e.getHost() + (e.getPort() != -1 ? ":" + e.getPort() : ""))
|
||||
final String target = Streams.stream(endpoints)
|
||||
.map(e -> e.getHost() + (e.getPort() != -1 ? (":" + e.getPort()) : ""))
|
||||
.distinct()
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
|
@ -159,8 +164,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns the auth user
|
||||
*
|
||||
* @return the user.
|
||||
*/
|
||||
public ByteSequence user() {
|
||||
return user;
|
||||
|
@ -174,15 +177,13 @@ public final class ClientBuilder implements Cloneable {
|
|||
* @throws NullPointerException if user is <code>null</code>
|
||||
*/
|
||||
public ClientBuilder user(ByteSequence user) {
|
||||
Objects.requireNonNull(user, "user can't be null");
|
||||
checkNotNull(user, "user can't be null");
|
||||
this.user = user;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the auth password
|
||||
*
|
||||
* @return the password.
|
||||
*/
|
||||
public ByteSequence password() {
|
||||
return password;
|
||||
|
@ -196,15 +197,13 @@ public final class ClientBuilder implements Cloneable {
|
|||
* @throws NullPointerException if password is <code>null</code>
|
||||
*/
|
||||
public ClientBuilder password(ByteSequence password) {
|
||||
Objects.requireNonNull(password, "password can't be null");
|
||||
checkNotNull(password, "password can't be null");
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespace of each key used
|
||||
*
|
||||
* @return the namespace.
|
||||
*/
|
||||
public ByteSequence namespace() {
|
||||
return namespace;
|
||||
|
@ -219,15 +218,13 @@ public final class ClientBuilder implements Cloneable {
|
|||
* @throws NullPointerException if namespace is <code>null</code>
|
||||
*/
|
||||
public ClientBuilder namespace(ByteSequence namespace) {
|
||||
Objects.requireNonNull(namespace, "namespace can't be null");
|
||||
checkNotNull(namespace, "namespace can't be null");
|
||||
this.namespace = namespace;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the executor service
|
||||
*
|
||||
* @return the executor service.
|
||||
*/
|
||||
public ExecutorService executorService() {
|
||||
return executorService;
|
||||
|
@ -241,7 +238,7 @@ public final class ClientBuilder implements Cloneable {
|
|||
* @throws NullPointerException if executorService is <code>null</code>
|
||||
*/
|
||||
public ClientBuilder executorService(ExecutorService executorService) {
|
||||
Objects.requireNonNull(executorService, "executorService can't be null");
|
||||
checkNotNull(executorService, "executorService can't be null");
|
||||
this.executorService = executorService;
|
||||
return this;
|
||||
}
|
||||
|
@ -254,7 +251,7 @@ public final class ClientBuilder implements Cloneable {
|
|||
* @throws NullPointerException if loadBalancerPolicy is <code>null</code>
|
||||
*/
|
||||
public ClientBuilder loadBalancerPolicy(String loadBalancerPolicy) {
|
||||
Objects.requireNonNull(loadBalancerPolicy, "loadBalancerPolicy can't be null");
|
||||
checkNotNull(loadBalancerPolicy, "loadBalancerPolicy can't be null");
|
||||
this.loadBalancerPolicy = loadBalancerPolicy;
|
||||
return this;
|
||||
}
|
||||
|
@ -270,8 +267,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns the ssl context
|
||||
*
|
||||
* @return the ssl context.
|
||||
*/
|
||||
public SslContext sslContext() {
|
||||
return sslContext;
|
||||
|
@ -305,8 +300,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns The authority used to authenticate connections to servers.
|
||||
*
|
||||
* @return the authority.
|
||||
*/
|
||||
public String authority() {
|
||||
return authority;
|
||||
|
@ -325,8 +318,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns the maximum message size allowed for a single gRPC frame.
|
||||
*
|
||||
* @return max inbound message size.
|
||||
*/
|
||||
public Integer maxInboundMessageSize() {
|
||||
return maxInboundMessageSize;
|
||||
|
@ -345,8 +336,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns the headers to be added to http request headers
|
||||
*
|
||||
* @return headers.
|
||||
*/
|
||||
public Map<Metadata.Key<?>, Object> headers() {
|
||||
return headers == null ? Collections.emptyMap() : Collections.unmodifiableMap(headers);
|
||||
|
@ -383,8 +372,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns the headers to be added to auth request headers
|
||||
*
|
||||
* @return auth headers.
|
||||
*/
|
||||
public Map<Metadata.Key<?>, Object> authHeaders() {
|
||||
return authHeaders == null ? Collections.emptyMap() : Collections.unmodifiableMap(authHeaders);
|
||||
|
@ -421,8 +408,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns the interceptors
|
||||
*
|
||||
* @return the interceptors.
|
||||
*/
|
||||
public List<ClientInterceptor> interceptors() {
|
||||
return interceptors;
|
||||
|
@ -460,8 +445,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns the auth interceptors
|
||||
*
|
||||
* @return the interceptors.
|
||||
*/
|
||||
public List<ClientInterceptor> authInterceptors() {
|
||||
return authInterceptors;
|
||||
|
@ -499,8 +482,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns The delay between retries.
|
||||
*
|
||||
* @return the retry delay.
|
||||
*/
|
||||
public long retryDelay() {
|
||||
return retryDelay;
|
||||
|
@ -519,8 +500,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns the max backing off delay between retries
|
||||
*
|
||||
* @return max retry delay.
|
||||
*/
|
||||
public long retryMaxDelay() {
|
||||
return retryMaxDelay;
|
||||
|
@ -537,31 +516,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the max number of retry attempts
|
||||
*
|
||||
* @return max retry attempts.
|
||||
*/
|
||||
public int retryMaxAttempts() {
|
||||
return retryMaxAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the max number of retry attempts
|
||||
*
|
||||
* @param retryMaxAttempts The max retry attempts.
|
||||
* @return this builder
|
||||
*/
|
||||
public ClientBuilder retryMaxAttempts(int retryMaxAttempts) {
|
||||
this.retryMaxAttempts = retryMaxAttempts;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keep alive time.
|
||||
*
|
||||
* @return keep alive time.
|
||||
*/
|
||||
public Duration keepaliveTime() {
|
||||
return keepaliveTime;
|
||||
}
|
||||
|
@ -580,11 +534,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keep alive time out.
|
||||
*
|
||||
* @return keep alive time out.
|
||||
*/
|
||||
public Duration keepaliveTimeout() {
|
||||
return keepaliveTimeout;
|
||||
}
|
||||
|
@ -617,8 +566,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns he retries period unit.
|
||||
*
|
||||
* @return the chrono unit.
|
||||
*/
|
||||
public ChronoUnit retryChronoUnit() {
|
||||
return retryChronoUnit;
|
||||
|
@ -637,8 +584,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns the retries max duration.
|
||||
*
|
||||
* @return retry max duration.
|
||||
*/
|
||||
public Duration retryMaxDuration() {
|
||||
return retryMaxDuration;
|
||||
|
@ -646,8 +591,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
|
||||
/**
|
||||
* Returns the connect timeout.
|
||||
*
|
||||
* @return connect timeout.
|
||||
*/
|
||||
public Duration connectTimeout() {
|
||||
return connectTimeout;
|
||||
|
@ -709,30 +652,6 @@ public final class ClientBuilder implements Cloneable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Vertx instance.
|
||||
*
|
||||
* @return the vertx instance.
|
||||
*/
|
||||
public Vertx vertx() {
|
||||
return vertx;
|
||||
}
|
||||
|
||||
/**
|
||||
* configure Vertx instance.
|
||||
*
|
||||
* @param vertx Vertx instance to use.
|
||||
* @return this builder to train
|
||||
* @throws IllegalArgumentException if vertx is null
|
||||
*/
|
||||
public ClientBuilder vertx(Vertx vertx) {
|
||||
Preconditions.checkArgument(vertx != null, "vertx can't be null");
|
||||
|
||||
this.vertx = vertx;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* build a new Client.
|
||||
*
|
||||
|
@ -740,15 +659,13 @@ public final class ClientBuilder implements Cloneable {
|
|||
* @throws EtcdException if client experiences build error.
|
||||
*/
|
||||
public Client build() {
|
||||
Preconditions.checkState(target != null, "please configure etcd server endpoints before build.");
|
||||
checkState(target != null, "please configure etcd server endpoints before build.");
|
||||
|
||||
return new ClientImpl(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this builder
|
||||
*
|
||||
* @return a copy of the builder.
|
||||
*/
|
||||
public ClientBuilder copy() {
|
||||
try {
|
||||
|
|
|
@ -14,35 +14,50 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.impl;
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.ClientBuilder;
|
||||
import io.etcd.jetcd.support.Util;
|
||||
import io.grpc.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.etcd.jetcd.auth.AuthInterceptor;
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.ClientCall;
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.netty.NegotiationType;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
import io.grpc.stub.AbstractStub;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.VertxOptions;
|
||||
import io.vertx.grpc.VertxChannelBuilder;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import net.jodah.failsafe.Failsafe;
|
||||
import net.jodah.failsafe.RetryPolicy;
|
||||
|
||||
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.toEtcdException;
|
||||
|
||||
final class ClientConnectionManager {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ClientConnectionManager.class);
|
||||
|
||||
private final Object lock;
|
||||
private final ClientBuilder builder;
|
||||
private final ExecutorService executorService;
|
||||
private final AuthCredential credential;
|
||||
private volatile Vertx vertx;
|
||||
private final AuthInterceptor authInterceptor;
|
||||
private volatile ManagedChannel managedChannel;
|
||||
|
||||
ClientConnectionManager(ClientBuilder builder) {
|
||||
|
@ -52,19 +67,14 @@ final class ClientConnectionManager {
|
|||
ClientConnectionManager(ClientBuilder builder, ManagedChannel managedChannel) {
|
||||
this.lock = new Object();
|
||||
this.builder = builder;
|
||||
this.authInterceptor = new AuthInterceptor(builder);
|
||||
this.managedChannel = managedChannel;
|
||||
this.credential = new AuthCredential(this);
|
||||
|
||||
if (builder.executorService() == null) {
|
||||
// default to daemon
|
||||
this.executorService = Executors.newCachedThreadPool(Util.createThreadFactory("jetcd-", true));
|
||||
this.executorService = Executors.newCachedThreadPool();
|
||||
} else {
|
||||
this.executorService = builder.executorService();
|
||||
}
|
||||
|
||||
if (builder.vertx() != null) {
|
||||
this.vertx = builder.vertx();
|
||||
}
|
||||
}
|
||||
|
||||
ManagedChannel getChannel() {
|
||||
|
@ -87,12 +97,8 @@ final class ClientConnectionManager {
|
|||
return executorService;
|
||||
}
|
||||
|
||||
ClientBuilder builder() {
|
||||
return builder;
|
||||
}
|
||||
|
||||
AuthCredential authCredential() {
|
||||
return this.credential;
|
||||
AuthInterceptor authInterceptor() {
|
||||
return authInterceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,15 +113,12 @@ final class ClientConnectionManager {
|
|||
}
|
||||
|
||||
private <T extends AbstractStub<T>> T newStub(Function<ManagedChannel, T> stubCustomizer, ManagedChannel channel) {
|
||||
T stub = stubCustomizer.apply(channel);
|
||||
final T stub = stubCustomizer.apply(channel);
|
||||
if (builder.waitForReady()) {
|
||||
stub = stub.withWaitForReady();
|
||||
return stub.withWaitForReady();
|
||||
} else {
|
||||
return stub;
|
||||
}
|
||||
if (builder.user() != null && builder.password() != null) {
|
||||
stub = stub.withCallCredentials(this.authCredential());
|
||||
}
|
||||
|
||||
return stub;
|
||||
}
|
||||
|
||||
void close() {
|
||||
|
@ -123,9 +126,6 @@ final class ClientConnectionManager {
|
|||
if (managedChannel != null) {
|
||||
managedChannel.shutdownNow();
|
||||
}
|
||||
if (vertx != null) {
|
||||
vertx.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.executorService() == null) {
|
||||
|
@ -133,8 +133,7 @@ final class ClientConnectionManager {
|
|||
}
|
||||
}
|
||||
|
||||
<T extends AbstractStub<T>, R> CompletableFuture<R> withNewChannel(
|
||||
String target,
|
||||
<T extends AbstractStub<T>, R> CompletableFuture<R> withNewChannel(String target,
|
||||
Function<ManagedChannel, T> stubCustomizer,
|
||||
Function<T, CompletableFuture<R>> stubConsumer) {
|
||||
|
||||
|
@ -149,17 +148,19 @@ final class ClientConnectionManager {
|
|||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ManagedChannelBuilder<?> defaultChannelBuilder() {
|
||||
return defaultChannelBuilder(builder.target());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@SuppressWarnings("rawtypes")
|
||||
ManagedChannelBuilder<?> defaultChannelBuilder(String target) {
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException("At least one endpoint should be provided");
|
||||
}
|
||||
|
||||
final VertxChannelBuilder channelBuilder = VertxChannelBuilder.forTarget(vertx(), target);
|
||||
final NettyChannelBuilder channelBuilder = NettyChannelBuilder.forTarget(target);
|
||||
|
||||
if (builder.authority() != null) {
|
||||
channelBuilder.overrideAuthority(builder.authority());
|
||||
|
@ -168,10 +169,10 @@ final class ClientConnectionManager {
|
|||
channelBuilder.maxInboundMessageSize(builder.maxInboundMessageSize());
|
||||
}
|
||||
if (builder.sslContext() != null) {
|
||||
channelBuilder.nettyBuilder().negotiationType(NegotiationType.TLS);
|
||||
channelBuilder.nettyBuilder().sslContext(builder.sslContext());
|
||||
channelBuilder.negotiationType(NegotiationType.TLS);
|
||||
channelBuilder.sslContext(builder.sslContext());
|
||||
} else {
|
||||
channelBuilder.nettyBuilder().negotiationType(NegotiationType.PLAINTEXT);
|
||||
channelBuilder.negotiationType(NegotiationType.PLAINTEXT);
|
||||
}
|
||||
|
||||
if (builder.keepaliveTime() != null) {
|
||||
|
@ -184,24 +185,22 @@ final class ClientConnectionManager {
|
|||
channelBuilder.keepAliveWithoutCalls(builder.keepaliveWithoutCalls());
|
||||
}
|
||||
if (builder.connectTimeout() != null) {
|
||||
channelBuilder.nettyBuilder().withOption(ChannelOption.CONNECT_TIMEOUT_MILLIS,
|
||||
(int) builder.connectTimeout().toMillis());
|
||||
channelBuilder.withOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) builder.connectTimeout().toMillis());
|
||||
}
|
||||
|
||||
if (builder.loadBalancerPolicy() != null) {
|
||||
channelBuilder.defaultLoadBalancingPolicy(builder.loadBalancerPolicy());
|
||||
} else {
|
||||
channelBuilder.defaultLoadBalancingPolicy("round_robin");
|
||||
channelBuilder.defaultLoadBalancingPolicy("pick_first");
|
||||
}
|
||||
|
||||
channelBuilder.intercept(authInterceptor);
|
||||
|
||||
if (builder.headers() != null) {
|
||||
channelBuilder.intercept(new ClientInterceptor() {
|
||||
@Override
|
||||
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
|
||||
MethodDescriptor<ReqT, RespT> method,
|
||||
CallOptions callOptions,
|
||||
Channel next) {
|
||||
|
||||
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
|
||||
CallOptions callOptions, Channel next) {
|
||||
return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
|
||||
@Override
|
||||
public void start(Listener<RespT> responseListener, Metadata headers) {
|
||||
|
@ -220,15 +219,59 @@ final class ClientConnectionManager {
|
|||
return channelBuilder;
|
||||
}
|
||||
|
||||
Vertx vertx() {
|
||||
if (this.vertx == null) {
|
||||
synchronized (this.lock) {
|
||||
if (this.vertx == null) {
|
||||
this.vertx = Vertx.vertx(new VertxOptions().setUseDaemonThread(true));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* execute the task and retry it in case of failure.
|
||||
*
|
||||
* @param task a function that returns a new ListenableFuture.
|
||||
* @param resultConvert a function that converts Type S to Type T.
|
||||
* @param <S> Source type
|
||||
* @param <T> Converted Type.
|
||||
* @return a CompletableFuture with type T.
|
||||
*/
|
||||
public <S, T> CompletableFuture<T> execute(Callable<ListenableFuture<S>> task, Function<S, T> resultConvert) {
|
||||
return execute(task, resultConvert, Util::isRetryable);
|
||||
}
|
||||
|
||||
/**
|
||||
* execute the task and retry it in case of failure.
|
||||
*
|
||||
* @param task a function that returns a new SourceFuture.
|
||||
* @param resultConvert a function that converts Type S to Type T.
|
||||
* @param doRetry a function that determines the retry condition base on SourceFuture error.
|
||||
* @param <S> Source type
|
||||
* @param <T> Converted Type.
|
||||
* @return a CompletableFuture with type T.
|
||||
*/
|
||||
@SuppressWarnings("FutureReturnValueIgnored")
|
||||
public <S, T> CompletableFuture<T> execute(
|
||||
Callable<ListenableFuture<S>> task,
|
||||
Function<S, T> resultConvert,
|
||||
Predicate<Throwable> doRetry) {
|
||||
|
||||
RetryPolicy<CompletableFuture<S>> retryPolicy = new RetryPolicy<CompletableFuture<S>>().handleIf(doRetry)
|
||||
.onRetriesExceeded(e -> LOGGER.warn("maximum number of auto retries reached"))
|
||||
.withBackoff(builder.retryDelay(), builder.retryMaxDelay(), builder.retryChronoUnit());
|
||||
|
||||
if (builder.retryMaxDuration() != null) {
|
||||
retryPolicy = retryPolicy.withMaxDuration(builder.retryMaxDuration());
|
||||
}
|
||||
|
||||
return this.vertx;
|
||||
return Failsafe.with(retryPolicy).with(executorService)
|
||||
.getAsyncExecution(execution -> {
|
||||
CompletableFuture<S> wrappedFuture = new CompletableFuture<>();
|
||||
ListenableFuture<S> future = task.call();
|
||||
future.addListener(() -> {
|
||||
try {
|
||||
wrappedFuture.complete(future.get());
|
||||
execution.complete(wrappedFuture);
|
||||
} catch (Exception error) {
|
||||
if (!execution.retryOn(error)) {
|
||||
// permanent failure
|
||||
wrappedFuture.completeExceptionally(error);
|
||||
}
|
||||
}
|
||||
}, executorService);
|
||||
}).thenCompose(f -> f.thenApply(resultConvert));
|
||||
}
|
||||
|
||||
}
|
|
@ -14,45 +14,35 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.impl;
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.Client;
|
||||
import io.etcd.jetcd.ClientBuilder;
|
||||
import io.etcd.jetcd.Cluster;
|
||||
import io.etcd.jetcd.Election;
|
||||
import io.etcd.jetcd.KV;
|
||||
import io.etcd.jetcd.Lease;
|
||||
import io.etcd.jetcd.Lock;
|
||||
import io.etcd.jetcd.Maintenance;
|
||||
import io.etcd.jetcd.Watch;
|
||||
import io.etcd.jetcd.support.MemorizingClientSupplier;
|
||||
import io.etcd.jetcd.support.MemoizingClientSupplier;
|
||||
|
||||
/**
|
||||
* Etcd Client.
|
||||
*/
|
||||
public final class ClientImpl implements Client {
|
||||
final class ClientImpl implements Client {
|
||||
|
||||
private final ClientConnectionManager connectionManager;
|
||||
private final MemorizingClientSupplier<KV> kvClient;
|
||||
private final MemorizingClientSupplier<Auth> authClient;
|
||||
private final MemorizingClientSupplier<Maintenance> maintenanceClient;
|
||||
private final MemorizingClientSupplier<Cluster> clusterClient;
|
||||
private final MemorizingClientSupplier<Lease> leaseClient;
|
||||
private final MemorizingClientSupplier<Watch> watchClient;
|
||||
private final MemorizingClientSupplier<Lock> lockClient;
|
||||
private final MemorizingClientSupplier<Election> electionClient;
|
||||
private final MemoizingClientSupplier<KV> kvClient;
|
||||
private final MemoizingClientSupplier<Auth> authClient;
|
||||
private final MemoizingClientSupplier<Maintenance> maintenanceClient;
|
||||
private final MemoizingClientSupplier<Cluster> clusterClient;
|
||||
private final MemoizingClientSupplier<Lease> leaseClient;
|
||||
private final MemoizingClientSupplier<Watch> watchClient;
|
||||
private final MemoizingClientSupplier<Lock> lockClient;
|
||||
private final MemoizingClientSupplier<Election> electionClient;
|
||||
|
||||
public ClientImpl(ClientBuilder clientBuilder) {
|
||||
this.connectionManager = new ClientConnectionManager(clientBuilder.copy());
|
||||
this.kvClient = new MemorizingClientSupplier<>(() -> new KVImpl(this.connectionManager));
|
||||
this.authClient = new MemorizingClientSupplier<>(() -> new AuthImpl(this.connectionManager));
|
||||
this.maintenanceClient = new MemorizingClientSupplier<>(() -> new MaintenanceImpl(this.connectionManager));
|
||||
this.clusterClient = new MemorizingClientSupplier<>(() -> new ClusterImpl(this.connectionManager));
|
||||
this.leaseClient = new MemorizingClientSupplier<>(() -> new LeaseImpl(this.connectionManager));
|
||||
this.watchClient = new MemorizingClientSupplier<>(() -> new WatchImpl(this.connectionManager));
|
||||
this.lockClient = new MemorizingClientSupplier<>(() -> new LockImpl(this.connectionManager));
|
||||
this.electionClient = new MemorizingClientSupplier<>(() -> new ElectionImpl(this.connectionManager));
|
||||
this.kvClient = new MemoizingClientSupplier<>(() -> new KVImpl(this.connectionManager));
|
||||
this.authClient = new MemoizingClientSupplier<>(() -> new AuthImpl(this.connectionManager));
|
||||
this.maintenanceClient = new MemoizingClientSupplier<>(() -> new MaintenanceImpl(this.connectionManager));
|
||||
this.clusterClient = new MemoizingClientSupplier<>(() -> new ClusterImpl(this.connectionManager));
|
||||
this.leaseClient = new MemoizingClientSupplier<>(() -> new LeaseImpl(this.connectionManager));
|
||||
this.watchClient = new MemoizingClientSupplier<>(() -> new WatchImpl(this.connectionManager));
|
||||
this.lockClient = new MemoizingClientSupplier<>(() -> new LockImpl(this.connectionManager));
|
||||
this.electionClient = new MemoizingClientSupplier<>(() -> new ElectionImpl(this.connectionManager));
|
||||
}
|
||||
|
||||
@Override
|
|
@ -39,22 +39,13 @@ public interface Cluster extends CloseableClient {
|
|||
CompletableFuture<MemberListResponse> listMember();
|
||||
|
||||
/**
|
||||
* add a non-learner new member into the cluster.
|
||||
* add a new member into the cluster.
|
||||
*
|
||||
* @param peerAddrs the peer addresses of the new member
|
||||
* @return the response
|
||||
*/
|
||||
CompletableFuture<MemberAddResponse> addMember(List<URI> peerAddrs);
|
||||
|
||||
/**
|
||||
* add a new member into the cluster.
|
||||
*
|
||||
* @param peerAddrs the peer addresses of the new member
|
||||
* @param isLearner whether the member is raft learner
|
||||
* @return the response
|
||||
*/
|
||||
CompletableFuture<MemberAddResponse> addMember(List<URI> peerAddrs, boolean isLearner);
|
||||
|
||||
/**
|
||||
* removes an existing member from the cluster.
|
||||
*
|
||||
|
|
|
@ -14,19 +14,18 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.impl;
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.etcd.jetcd.Cluster;
|
||||
import io.etcd.jetcd.api.ClusterGrpc;
|
||||
import io.etcd.jetcd.api.MemberAddRequest;
|
||||
import io.etcd.jetcd.api.MemberListRequest;
|
||||
import io.etcd.jetcd.api.MemberRemoveRequest;
|
||||
import io.etcd.jetcd.api.MemberUpdateRequest;
|
||||
import io.etcd.jetcd.api.VertxClusterGrpc;
|
||||
import io.etcd.jetcd.cluster.MemberAddResponse;
|
||||
import io.etcd.jetcd.cluster.MemberListResponse;
|
||||
import io.etcd.jetcd.cluster.MemberRemoveResponse;
|
||||
|
@ -35,14 +34,14 @@ import io.etcd.jetcd.cluster.MemberUpdateResponse;
|
|||
/**
|
||||
* Implementation of cluster client.
|
||||
*/
|
||||
final class ClusterImpl extends Impl implements Cluster {
|
||||
final class ClusterImpl implements Cluster {
|
||||
|
||||
private final VertxClusterGrpc.ClusterVertxStub stub;
|
||||
private final ClusterGrpc.ClusterFutureStub stub;
|
||||
private final ClientConnectionManager connectionManager;
|
||||
|
||||
ClusterImpl(ClientConnectionManager connectionManager) {
|
||||
super(connectionManager);
|
||||
|
||||
this.stub = connectionManager.newStub(VertxClusterGrpc::newVertxStub);
|
||||
this.connectionManager = connectionManager;
|
||||
this.stub = connectionManager.newStub(ClusterGrpc::newFutureStub);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,37 +49,27 @@ final class ClusterImpl extends Impl implements Cluster {
|
|||
*/
|
||||
@Override
|
||||
public CompletableFuture<MemberListResponse> listMember() {
|
||||
return completable(
|
||||
return Util.toCompletableFuture(
|
||||
this.stub.memberList(MemberListRequest.getDefaultInstance()),
|
||||
MemberListResponse::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* add a non-learner new member into the cluster.
|
||||
*
|
||||
* @param peerAddrs the peer addresses of the new member
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<MemberAddResponse> addMember(List<URI> peerAddrs) {
|
||||
return addMember(peerAddrs, false);
|
||||
MemberListResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
/**
|
||||
* add a new member into the cluster.
|
||||
*
|
||||
* @param peerAddrs the peer addresses of the new member
|
||||
* @param isLearner whether the member is raft learner
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<MemberAddResponse> addMember(List<URI> peerAddrs, boolean isLearner) {
|
||||
public CompletableFuture<MemberAddResponse> addMember(List<URI> peerAddrs) {
|
||||
MemberAddRequest memberAddRequest = MemberAddRequest.newBuilder()
|
||||
.addAllPeerURLs(peerAddrs.stream().map(URI::toString).collect(Collectors.toList()))
|
||||
.setIsLearner(isLearner)
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
return Util.toCompletableFuture(
|
||||
this.stub.memberAdd(memberAddRequest),
|
||||
MemberAddResponse::new);
|
||||
MemberAddResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,9 +83,10 @@ final class ClusterImpl extends Impl implements Cluster {
|
|||
.setID(memberID)
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
return Util.toCompletableFuture(
|
||||
this.stub.memberRemove(memberRemoveRequest),
|
||||
MemberRemoveResponse::new);
|
||||
MemberRemoveResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,12 +98,12 @@ final class ClusterImpl extends Impl implements Cluster {
|
|||
@Override
|
||||
public CompletableFuture<MemberUpdateResponse> updateMember(long memberID, List<URI> peerAddrs) {
|
||||
MemberUpdateRequest memberUpdateRequest = MemberUpdateRequest.newBuilder()
|
||||
.addAllPeerURLs(peerAddrs.stream().map(URI::toString).collect(Collectors.toList()))
|
||||
.setID(memberID)
|
||||
.addAllPeerURLs(peerAddrs.stream().map(URI::toString).collect(Collectors.toList())).setID(memberID)
|
||||
.build();
|
||||
|
||||
return completable(
|
||||
return Util.toCompletableFuture(
|
||||
this.stub.memberUpdate(memberUpdateRequest),
|
||||
MemberUpdateResponse::new);
|
||||
MemberUpdateResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
}
|
|
@ -22,11 +22,11 @@ import io.grpc.Metadata;
|
|||
* Constants of Etcd.
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
public static final String TOKEN = "token";
|
||||
public static final ByteSequence NULL_KEY = ByteSequence.from(new byte[] { '\0' });
|
||||
|
||||
public static final Metadata.Key<String> REQUIRE_LEADER_KEY = Metadata.Key.of(
|
||||
"hasleader",
|
||||
public static final Metadata.Key<String> REQUIRE_LEADER_KEY = Metadata.Key.of("hasleader",
|
||||
Metadata.ASCII_STRING_MARSHALLER);
|
||||
|
||||
public static final String REQUIRE_LEADER_VALUE = "true";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.etcd.jetcd.api.CampaignRequest;
|
||||
import io.etcd.jetcd.api.ElectionGrpc;
|
||||
import io.etcd.jetcd.api.LeaderRequest;
|
||||
import io.etcd.jetcd.api.ProclaimRequest;
|
||||
import io.etcd.jetcd.api.ResignRequest;
|
||||
import io.etcd.jetcd.common.exception.EtcdExceptionFactory;
|
||||
import io.etcd.jetcd.election.CampaignResponse;
|
||||
import io.etcd.jetcd.election.LeaderKey;
|
||||
import io.etcd.jetcd.election.LeaderResponse;
|
||||
import io.etcd.jetcd.election.NoLeaderException;
|
||||
import io.etcd.jetcd.election.NotLeaderException;
|
||||
import io.etcd.jetcd.election.ProclaimResponse;
|
||||
import io.etcd.jetcd.election.ResignResponse;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
final class ElectionImpl implements Election {
|
||||
private final ElectionGrpc.ElectionStub stub;
|
||||
private final ByteSequence namespace;
|
||||
|
||||
ElectionImpl(ClientConnectionManager connectionManager) {
|
||||
this.stub = connectionManager.newStub(ElectionGrpc::newStub);
|
||||
this.namespace = connectionManager.getNamespace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CampaignResponse> campaign(ByteSequence electionName, long leaseId, ByteSequence proposal) {
|
||||
checkNotNull(electionName, "election name should not be null");
|
||||
checkNotNull(proposal, "proposal should not be null");
|
||||
|
||||
CampaignRequest request = CampaignRequest.newBuilder()
|
||||
.setName(Util.prefixNamespace(electionName.getByteString(), namespace))
|
||||
.setValue(proposal.getByteString())
|
||||
.setLease(leaseId)
|
||||
.build();
|
||||
|
||||
final StreamObserverDelegate<io.etcd.jetcd.api.CampaignResponse, CampaignResponse> observer = new StreamObserverDelegate<>(
|
||||
CampaignResponse::new);
|
||||
stub.campaign(request, observer);
|
||||
|
||||
return observer.getFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ProclaimResponse> proclaim(LeaderKey leaderKey, ByteSequence proposal) {
|
||||
checkNotNull(leaderKey, "leader key should not be null");
|
||||
checkNotNull(proposal, "proposal should not be null");
|
||||
|
||||
io.etcd.jetcd.api.LeaderKey leader = io.etcd.jetcd.api.LeaderKey.newBuilder()
|
||||
.setKey(leaderKey.getKey()).setName(leaderKey.getName())
|
||||
.setLease(leaderKey.getLease()).setRev(leaderKey.getRevision())
|
||||
.build();
|
||||
ProclaimRequest request = ProclaimRequest.newBuilder()
|
||||
.setLeader(leader).setValue(proposal.getByteString())
|
||||
.build();
|
||||
|
||||
final StreamObserverDelegate<io.etcd.jetcd.api.ProclaimResponse, ProclaimResponse> observer = new StreamObserverDelegate<>(
|
||||
ProclaimResponse::new);
|
||||
stub.proclaim(request, observer);
|
||||
|
||||
return observer.getFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LeaderResponse> leader(ByteSequence electionName) {
|
||||
checkNotNull(electionName, "election name should not be null");
|
||||
|
||||
LeaderRequest request = LeaderRequest.newBuilder()
|
||||
.setName(Util.prefixNamespace(electionName.getByteString(), namespace))
|
||||
.build();
|
||||
|
||||
final StreamObserverDelegate<io.etcd.jetcd.api.LeaderResponse, LeaderResponse> observer = new StreamObserverDelegate<>(
|
||||
input -> new LeaderResponse(input, namespace));
|
||||
stub.leader(request, observer);
|
||||
|
||||
return observer.getFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void observe(ByteSequence electionName, Listener listener) {
|
||||
checkNotNull(electionName, "election name should not be null");
|
||||
checkNotNull(listener, "listener should not be null");
|
||||
|
||||
LeaderRequest request = LeaderRequest.newBuilder().setName(electionName.getByteString()).build();
|
||||
|
||||
stub.observe(request, new StreamObserver<io.etcd.jetcd.api.LeaderResponse>() {
|
||||
@Override
|
||||
public void onNext(io.etcd.jetcd.api.LeaderResponse value) {
|
||||
listener.onNext(new LeaderResponse(value, namespace));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable error) {
|
||||
listener.onError(EtcdExceptionFactory.toEtcdException(error));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
listener.onCompleted();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ResignResponse> resign(LeaderKey leaderKey) {
|
||||
checkNotNull(leaderKey, "leader key should not be null");
|
||||
|
||||
io.etcd.jetcd.api.LeaderKey leader = io.etcd.jetcd.api.LeaderKey.newBuilder()
|
||||
.setKey(leaderKey.getKey()).setName(leaderKey.getName())
|
||||
.setLease(leaderKey.getLease()).setRev(leaderKey.getRevision())
|
||||
.build();
|
||||
ResignRequest request = ResignRequest.newBuilder().setLeader(leader).build();
|
||||
|
||||
final StreamObserverDelegate<io.etcd.jetcd.api.ResignResponse, ResignResponse> observer = new StreamObserverDelegate<>(
|
||||
ResignResponse::new);
|
||||
stub.resign(request, observer);
|
||||
|
||||
return observer.getFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
private static class StreamObserverDelegate<S, T> implements StreamObserver<S> {
|
||||
private final CompletableFuture<T> future = new CompletableFuture<>();
|
||||
private final Function<S, T> converter;
|
||||
|
||||
public StreamObserverDelegate(Function<S, T> converter) {
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(S value) {
|
||||
future.complete(converter.apply(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable error) {
|
||||
if (error instanceof StatusRuntimeException) {
|
||||
StatusRuntimeException exception = (StatusRuntimeException) error;
|
||||
String description = exception.getStatus().getDescription();
|
||||
// different APIs use different messages. we cannot distinguish missing leader error otherwise,
|
||||
// because communicated status is always UNKNOWN
|
||||
if ("election: not leader".equals(description)) {
|
||||
future.completeExceptionally(NotLeaderException.INSTANCE);
|
||||
} else if ("election: no leader".equals(description)) {
|
||||
future.completeExceptionally(NoLeaderException.INSTANCE);
|
||||
}
|
||||
}
|
||||
future.completeExceptionally(EtcdExceptionFactory.toEtcdException(error));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
}
|
||||
|
||||
public CompletableFuture<T> getFuture() {
|
||||
return future;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,6 @@ import io.etcd.jetcd.options.CompactOption;
|
|||
import io.etcd.jetcd.options.DeleteOption;
|
||||
import io.etcd.jetcd.options.GetOption;
|
||||
import io.etcd.jetcd.options.PutOption;
|
||||
import io.etcd.jetcd.options.TxnOption;
|
||||
import io.etcd.jetcd.support.CloseableClient;
|
||||
|
||||
/**
|
||||
|
@ -116,12 +115,4 @@ public interface KV extends CloseableClient {
|
|||
* @return a Txn
|
||||
*/
|
||||
Txn txn();
|
||||
|
||||
/**
|
||||
* creates a transaction.
|
||||
*
|
||||
* @param option TxnOption
|
||||
* @return a Txn
|
||||
*/
|
||||
Txn txn(TxnOption option);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.etcd.jetcd.api.CompactionRequest;
|
||||
import io.etcd.jetcd.api.DeleteRangeRequest;
|
||||
import io.etcd.jetcd.api.KVGrpc;
|
||||
import io.etcd.jetcd.api.PutRequest;
|
||||
import io.etcd.jetcd.api.RangeRequest;
|
||||
import io.etcd.jetcd.kv.CompactResponse;
|
||||
import io.etcd.jetcd.kv.DeleteResponse;
|
||||
import io.etcd.jetcd.kv.GetResponse;
|
||||
import io.etcd.jetcd.kv.PutResponse;
|
||||
import io.etcd.jetcd.kv.TxnResponse;
|
||||
import io.etcd.jetcd.op.TxnImpl;
|
||||
import io.etcd.jetcd.options.CompactOption;
|
||||
import io.etcd.jetcd.options.DeleteOption;
|
||||
import io.etcd.jetcd.options.GetOption;
|
||||
import io.etcd.jetcd.options.OptionsUtil;
|
||||
import io.etcd.jetcd.options.PutOption;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static io.etcd.jetcd.options.OptionsUtil.toRangeRequestSortOrder;
|
||||
import static io.etcd.jetcd.options.OptionsUtil.toRangeRequestSortTarget;
|
||||
|
||||
/**
|
||||
* Implementation of etcd kv client.
|
||||
*/
|
||||
final class KVImpl implements KV {
|
||||
|
||||
private final ClientConnectionManager connectionManager;
|
||||
|
||||
private final KVGrpc.KVFutureStub stub;
|
||||
|
||||
private final ByteSequence namespace;
|
||||
|
||||
KVImpl(ClientConnectionManager connectionManager) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.stub = connectionManager.newStub(KVGrpc::newFutureStub);
|
||||
this.namespace = connectionManager.getNamespace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<PutResponse> put(ByteSequence key, ByteSequence value) {
|
||||
return this.put(key, value, PutOption.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<PutResponse> put(ByteSequence key, ByteSequence value, PutOption option) {
|
||||
checkNotNull(key, "key should not be null");
|
||||
checkNotNull(value, "value should not be null");
|
||||
checkNotNull(option, "option should not be null");
|
||||
|
||||
PutRequest request = PutRequest.newBuilder()
|
||||
.setKey(Util.prefixNamespace(key.getByteString(), namespace))
|
||||
.setValue(value.getByteString())
|
||||
.setLease(option.getLeaseId())
|
||||
.setPrevKv(option.getPrevKV())
|
||||
.build();
|
||||
|
||||
return connectionManager.execute(
|
||||
() -> stub.put(request),
|
||||
response -> new PutResponse(response, namespace),
|
||||
Util::isRetryable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<GetResponse> get(ByteSequence key) {
|
||||
return this.get(key, GetOption.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<GetResponse> get(ByteSequence key, GetOption option) {
|
||||
checkNotNull(key, "key should not be null");
|
||||
checkNotNull(option, "option should not be null");
|
||||
|
||||
RangeRequest.Builder builder = RangeRequest.newBuilder()
|
||||
.setKey(Util.prefixNamespace(key.getByteString(), namespace))
|
||||
.setCountOnly(option.isCountOnly())
|
||||
.setLimit(option.getLimit())
|
||||
.setRevision(option.getRevision())
|
||||
.setKeysOnly(option.isKeysOnly())
|
||||
.setSerializable(option.isSerializable())
|
||||
.setSortOrder(toRangeRequestSortOrder(option.getSortOrder()))
|
||||
.setSortTarget(toRangeRequestSortTarget(option.getSortField()))
|
||||
.setMinCreateRevision(option.getMinCreateRevision())
|
||||
.setMaxCreateRevision(option.getMaxCreateRevision())
|
||||
.setMinModRevision(option.getMinModRevision())
|
||||
.setMaxModRevision(option.getMaxModRevision());
|
||||
|
||||
option.getEndKey().map(endKey -> Util.prefixNamespaceToRangeEnd(endKey.getByteString(), namespace))
|
||||
.ifPresent(builder::setRangeEnd);
|
||||
|
||||
if (!option.getEndKey().isPresent() && option.isPrefix()) {
|
||||
ByteSequence endKey = OptionsUtil.prefixEndOf(key);
|
||||
builder.setRangeEnd(Util.prefixNamespaceToRangeEnd(endKey.getByteString(), namespace));
|
||||
}
|
||||
|
||||
RangeRequest request = builder.build();
|
||||
|
||||
return connectionManager.execute(
|
||||
() -> stub.range(request),
|
||||
response -> new GetResponse(response, namespace),
|
||||
Util::isRetryable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DeleteResponse> delete(ByteSequence key) {
|
||||
return this.delete(key, DeleteOption.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DeleteResponse> delete(ByteSequence key, DeleteOption option) {
|
||||
checkNotNull(key, "key should not be null");
|
||||
checkNotNull(option, "option should not be null");
|
||||
|
||||
DeleteRangeRequest.Builder builder = DeleteRangeRequest.newBuilder()
|
||||
.setKey(Util.prefixNamespace(key.getByteString(), namespace))
|
||||
.setPrevKv(option.isPrevKV());
|
||||
|
||||
option.getEndKey()
|
||||
.map(endKey -> Util.prefixNamespaceToRangeEnd(endKey.getByteString(), namespace))
|
||||
.ifPresent(builder::setRangeEnd);
|
||||
|
||||
if (!option.getEndKey().isPresent() && option.isPrefix()) {
|
||||
ByteSequence endKey = OptionsUtil.prefixEndOf(key);
|
||||
builder.setRangeEnd(Util.prefixNamespaceToRangeEnd(endKey.getByteString(), namespace));
|
||||
}
|
||||
|
||||
DeleteRangeRequest request = builder.build();
|
||||
|
||||
return connectionManager.execute(
|
||||
() -> stub.deleteRange(request),
|
||||
response -> new DeleteResponse(response, namespace),
|
||||
Util::isRetryable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CompactResponse> compact(long rev) {
|
||||
return this.compact(rev, CompactOption.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CompactResponse> compact(long rev, CompactOption option) {
|
||||
checkNotNull(option, "option should not be null");
|
||||
|
||||
CompactionRequest request = CompactionRequest.newBuilder()
|
||||
.setRevision(rev).setPhysical(option.isPhysical())
|
||||
.build();
|
||||
|
||||
return connectionManager.execute(
|
||||
() -> stub.compact(request),
|
||||
CompactResponse::new,
|
||||
Util::isRetryable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Txn txn() {
|
||||
return TxnImpl.newTxn(
|
||||
request -> connectionManager.execute(
|
||||
() -> stub.txn(request),
|
||||
response -> new TxnResponse(response, namespace), Util::isRetryable),
|
||||
namespace);
|
||||
}
|
||||
}
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import io.etcd.jetcd.support.Util;
|
||||
|
||||
/**
|
||||
* Etcd key value pair.
|
||||
*/
|
||||
|
@ -29,18 +27,13 @@ public class KeyValue {
|
|||
|
||||
public KeyValue(io.etcd.jetcd.api.KeyValue kv, ByteSequence namespace) {
|
||||
this.kv = kv;
|
||||
this.unprefixedKey = ByteSequence
|
||||
.from(kv.getKey().isEmpty() ? kv.getKey() : Util.unprefixNamespace(kv.getKey(), namespace));
|
||||
this.value = ByteSequence.from(kv.getValue());
|
||||
|
||||
this.unprefixedKey = ByteSequence.from(
|
||||
kv.getKey().isEmpty()
|
||||
? kv.getKey()
|
||||
: Util.unprefixNamespace(kv.getKey(), namespace));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key
|
||||
*
|
||||
* @return the key.
|
||||
*/
|
||||
public ByteSequence getKey() {
|
||||
return unprefixedKey;
|
||||
|
@ -48,8 +41,6 @@ public class KeyValue {
|
|||
|
||||
/**
|
||||
* Returns the value
|
||||
*
|
||||
* @return the value.
|
||||
*/
|
||||
public ByteSequence getValue() {
|
||||
return value;
|
||||
|
@ -57,8 +48,6 @@ public class KeyValue {
|
|||
|
||||
/**
|
||||
* Returns the create revision.
|
||||
*
|
||||
* @return the create revision.
|
||||
*/
|
||||
public long getCreateRevision() {
|
||||
return kv.getCreateRevision();
|
||||
|
@ -66,8 +55,6 @@ public class KeyValue {
|
|||
|
||||
/**
|
||||
* Returns the mod revision.
|
||||
*
|
||||
* @return the mod revision.
|
||||
*/
|
||||
public long getModRevision() {
|
||||
return kv.getModRevision();
|
||||
|
@ -75,8 +62,6 @@ public class KeyValue {
|
|||
|
||||
/**
|
||||
* Returns the version.
|
||||
*
|
||||
* @return the version.
|
||||
*/
|
||||
public long getVersion() {
|
||||
return kv.getVersion();
|
||||
|
@ -84,8 +69,6 @@ public class KeyValue {
|
|||
|
||||
/**
|
||||
* Returns the lease.
|
||||
*
|
||||
* @return the lease.
|
||||
*/
|
||||
public long getLease() {
|
||||
return kv.getLease();
|
||||
|
|
|
@ -0,0 +1,354 @@
|
|||
/*
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.etcd.jetcd.api.LeaseGrantRequest;
|
||||
import io.etcd.jetcd.api.LeaseGrpc;
|
||||
import io.etcd.jetcd.api.LeaseKeepAliveRequest;
|
||||
import io.etcd.jetcd.api.LeaseRevokeRequest;
|
||||
import io.etcd.jetcd.api.LeaseTimeToLiveRequest;
|
||||
import io.etcd.jetcd.common.exception.ErrorCode;
|
||||
import io.etcd.jetcd.lease.LeaseGrantResponse;
|
||||
import io.etcd.jetcd.lease.LeaseKeepAliveResponse;
|
||||
import io.etcd.jetcd.lease.LeaseRevokeResponse;
|
||||
import io.etcd.jetcd.lease.LeaseTimeToLiveResponse;
|
||||
import io.etcd.jetcd.options.LeaseOption;
|
||||
import io.etcd.jetcd.support.CloseableClient;
|
||||
import io.etcd.jetcd.support.Observers;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.newClosedLeaseClientException;
|
||||
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.newEtcdException;
|
||||
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.toEtcdException;
|
||||
|
||||
/**
|
||||
* Implementation of lease client.
|
||||
*/
|
||||
final class LeaseImpl implements Lease {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LeaseImpl.class);
|
||||
|
||||
/**
|
||||
* FIRST_KEEPALIVE_TIMEOUT_MS is the timeout for the first keepalive request
|
||||
* before the actual TTL is known to the lease client.
|
||||
*/
|
||||
private static final int FIRST_KEEPALIVE_TIMEOUT_MS = 5000;
|
||||
|
||||
private final ClientConnectionManager connectionManager;
|
||||
private final LeaseGrpc.LeaseFutureStub stub;
|
||||
private final LeaseGrpc.LeaseStub leaseStub;
|
||||
private final Map<Long, KeepAlive> keepAlives;
|
||||
/**
|
||||
* Timer schedule to send keep alive request.
|
||||
*/
|
||||
private final ListeningScheduledExecutorService scheduledExecutorService;
|
||||
private ScheduledFuture<?> keepAliveFuture;
|
||||
private ScheduledFuture<?> deadlineFuture;
|
||||
/**
|
||||
* KeepAlive Request Stream, put request into this stream to keep the lease alive.
|
||||
*/
|
||||
private StreamObserver<LeaseKeepAliveRequest> keepAliveRequestObserver;
|
||||
|
||||
/**
|
||||
* KeepAlive Response Streamer, receive keep alive response from this stream and update the
|
||||
* nextKeepAliveTime and deadline of the leases.
|
||||
*/
|
||||
private StreamObserver<io.etcd.jetcd.api.LeaseKeepAliveResponse> keepAliveResponseObserver;
|
||||
|
||||
/**
|
||||
* hasKeepAliveServiceStarted indicates whether the background keep alive service has started.
|
||||
*/
|
||||
private volatile boolean hasKeepAliveServiceStarted;
|
||||
private volatile boolean closed;
|
||||
|
||||
LeaseImpl(ClientConnectionManager connectionManager) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.stub = connectionManager.newStub(LeaseGrpc::newFutureStub);
|
||||
this.leaseStub = Util.applyRequireLeader(true, connectionManager.newStub(LeaseGrpc::newStub));
|
||||
this.keepAlives = new ConcurrentHashMap<>();
|
||||
this.scheduledExecutorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LeaseGrantResponse> grant(long ttl) {
|
||||
return connectionManager.execute(() -> this.stub.leaseGrant(LeaseGrantRequest.newBuilder().setTTL(ttl).build()),
|
||||
LeaseGrantResponse::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LeaseGrantResponse> grant(long ttl, long timeout, TimeUnit unit) {
|
||||
return connectionManager.execute(
|
||||
() -> this.stub.withDeadlineAfter(timeout, unit).leaseGrant(LeaseGrantRequest.newBuilder().setTTL(ttl).build()),
|
||||
LeaseGrantResponse::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LeaseRevokeResponse> revoke(long leaseId) {
|
||||
return connectionManager.execute(() -> this.stub.leaseRevoke(LeaseRevokeRequest.newBuilder().setID(leaseId).build()),
|
||||
LeaseRevokeResponse::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized CloseableClient keepAlive(long leaseId, StreamObserver<LeaseKeepAliveResponse> observer) {
|
||||
if (this.closed) {
|
||||
throw newClosedLeaseClientException();
|
||||
}
|
||||
|
||||
KeepAlive keepAlive = this.keepAlives.computeIfAbsent(leaseId, (key) -> new KeepAlive(leaseId));
|
||||
keepAlive.addObserver(observer);
|
||||
|
||||
if (!this.hasKeepAliveServiceStarted) {
|
||||
this.hasKeepAliveServiceStarted = true;
|
||||
this.start();
|
||||
}
|
||||
|
||||
return new CloseableClient() {
|
||||
@Override
|
||||
public void close() {
|
||||
keepAlive.removeObserver(observer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
this.closed = true;
|
||||
|
||||
if (!this.hasKeepAliveServiceStarted) { // hasKeepAliveServiceStarted hasn't started.
|
||||
return;
|
||||
}
|
||||
|
||||
this.keepAliveFuture.cancel(true);
|
||||
this.deadlineFuture.cancel(true);
|
||||
this.keepAliveRequestObserver.onCompleted();
|
||||
this.keepAliveResponseObserver.onCompleted();
|
||||
this.scheduledExecutorService.shutdownNow();
|
||||
|
||||
final Throwable errResp = newClosedLeaseClientException();
|
||||
|
||||
this.keepAlives.forEach((k, v) -> v.onError(errResp));
|
||||
this.keepAlives.clear();
|
||||
}
|
||||
|
||||
private synchronized void removeKeepAlive(long leaseId) {
|
||||
this.keepAlives.remove(leaseId);
|
||||
}
|
||||
|
||||
private void start() {
|
||||
this.sendKeepAliveExecutor();
|
||||
this.deadLineExecutor();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
this.keepAliveFuture.cancel(true);
|
||||
this.keepAliveRequestObserver.onCompleted();
|
||||
this.keepAliveResponseObserver.onCompleted();
|
||||
this.sendKeepAliveExecutor();
|
||||
}
|
||||
|
||||
private void sendKeepAliveExecutor() {
|
||||
this.keepAliveResponseObserver = Observers.observer(this::processKeepAliveResponse, error -> processOnError());
|
||||
this.keepAliveRequestObserver = this.leaseStub.leaseKeepAlive(this.keepAliveResponseObserver);
|
||||
this.keepAliveFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
|
||||
// send keep alive req to the leases whose next keep alive is before now.
|
||||
this.keepAlives.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().getNextKeepAlive() < System.currentTimeMillis()).map(Entry::getKey)
|
||||
.map(leaseId -> LeaseKeepAliveRequest.newBuilder().setID(leaseId).build())
|
||||
.forEach(keepAliveRequestObserver::onNext);
|
||||
|
||||
}, 0, 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private synchronized void processOnError() {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
Futures.addCallback(this.scheduledExecutorService.schedule(this::reset, 500, TimeUnit.MILLISECONDS),
|
||||
new FutureCallback<Object>() {
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
LOG.error("scheduled reset failed", throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Object result) {
|
||||
}
|
||||
}, this.scheduledExecutorService);
|
||||
}
|
||||
|
||||
private synchronized void processKeepAliveResponse(io.etcd.jetcd.api.LeaseKeepAliveResponse leaseKeepAliveResponse) {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
final long leaseID = leaseKeepAliveResponse.getID();
|
||||
final long ttl = leaseKeepAliveResponse.getTTL();
|
||||
final KeepAlive ka = this.keepAlives.get(leaseID);
|
||||
|
||||
if (ka == null) {
|
||||
// return if the corresponding keep alive has closed.
|
||||
return;
|
||||
}
|
||||
|
||||
if (ttl > 0) {
|
||||
long nextKeepAlive = System.currentTimeMillis() + ttl * 1000 / 3;
|
||||
ka.setNextKeepAlive(nextKeepAlive);
|
||||
ka.setDeadLine(System.currentTimeMillis() + ttl * 1000);
|
||||
ka.onNext(leaseKeepAliveResponse);
|
||||
} else {
|
||||
// lease expired; close all keep alive
|
||||
this.removeKeepAlive(leaseID);
|
||||
ka.onError(newEtcdException(ErrorCode.NOT_FOUND, "etcdserver: requested lease not found"));
|
||||
}
|
||||
}
|
||||
|
||||
private void deadLineExecutor() {
|
||||
this.deadlineFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
this.keepAlives.values().removeIf(ka -> {
|
||||
if (ka.getDeadLine() < now) {
|
||||
ka.onCompleted();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}, 0, 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LeaseKeepAliveResponse> keepAliveOnce(long leaseId) {
|
||||
CompletableFuture<LeaseKeepAliveResponse> future = new CompletableFuture<>();
|
||||
|
||||
StreamObserver<LeaseKeepAliveRequest> requestObserver = Observers.observe(
|
||||
this.leaseStub::leaseKeepAlive,
|
||||
response -> future.complete(new LeaseKeepAliveResponse(response)),
|
||||
throwable -> future.completeExceptionally(toEtcdException(throwable)));
|
||||
|
||||
// cancel grpc stream when leaseKeepAliveResponseCompletableFuture completes.
|
||||
CompletableFuture<LeaseKeepAliveResponse> answer = future
|
||||
.whenCompleteAsync((val, throwable) -> requestObserver.onCompleted(), connectionManager.getExecutorService());
|
||||
|
||||
requestObserver.onNext(LeaseKeepAliveRequest.newBuilder().setID(leaseId).build());
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LeaseTimeToLiveResponse> timeToLive(long leaseId, LeaseOption option) {
|
||||
checkNotNull(option, "LeaseOption should not be null");
|
||||
|
||||
LeaseTimeToLiveRequest leaseTimeToLiveRequest = LeaseTimeToLiveRequest.newBuilder()
|
||||
.setID(leaseId)
|
||||
.setKeys(option.isAttachedKeys())
|
||||
.build();
|
||||
|
||||
return connectionManager.execute(
|
||||
() -> this.stub.leaseTimeToLive(leaseTimeToLiveRequest),
|
||||
LeaseTimeToLiveResponse::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* The KeepAlive hold the keepAlive information for lease.
|
||||
*/
|
||||
private final class KeepAlive implements StreamObserver<io.etcd.jetcd.api.LeaseKeepAliveResponse> {
|
||||
private final List<StreamObserver<LeaseKeepAliveResponse>> observers;
|
||||
private final long leaseId;
|
||||
|
||||
private long deadLine;
|
||||
private long nextKeepAlive;
|
||||
|
||||
public KeepAlive(long leaseId) {
|
||||
this.nextKeepAlive = System.currentTimeMillis();
|
||||
this.deadLine = nextKeepAlive + FIRST_KEEPALIVE_TIMEOUT_MS;
|
||||
|
||||
this.observers = new CopyOnWriteArrayList<>();
|
||||
this.leaseId = leaseId;
|
||||
}
|
||||
|
||||
public long getDeadLine() {
|
||||
return deadLine;
|
||||
}
|
||||
|
||||
public void setDeadLine(long deadLine) {
|
||||
this.deadLine = deadLine;
|
||||
}
|
||||
|
||||
public void addObserver(StreamObserver<LeaseKeepAliveResponse> observer) {
|
||||
this.observers.add(observer);
|
||||
}
|
||||
|
||||
//removeObserver only would be called synchronously by close in KeepAliveListener, no need to get lock here
|
||||
public void removeObserver(StreamObserver<LeaseKeepAliveResponse> listener) {
|
||||
this.observers.remove(listener);
|
||||
if (this.observers.isEmpty()) {
|
||||
removeKeepAlive(leaseId);
|
||||
}
|
||||
}
|
||||
|
||||
public long getNextKeepAlive() {
|
||||
return nextKeepAlive;
|
||||
}
|
||||
|
||||
public void setNextKeepAlive(long nextKeepAlive) {
|
||||
this.nextKeepAlive = nextKeepAlive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(io.etcd.jetcd.api.LeaseKeepAliveResponse response) {
|
||||
for (StreamObserver<LeaseKeepAliveResponse> observer : observers) {
|
||||
observer.onNext(new LeaseKeepAliveResponse(response));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
for (StreamObserver<LeaseKeepAliveResponse> observer : observers) {
|
||||
observer.onError(toEtcdException(throwable));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
this.observers.forEach(StreamObserver::onCompleted);
|
||||
this.observers.clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.etcd.jetcd.api.lock.LockGrpc;
|
||||
import io.etcd.jetcd.api.lock.LockRequest;
|
||||
import io.etcd.jetcd.api.lock.UnlockRequest;
|
||||
import io.etcd.jetcd.lock.LockResponse;
|
||||
import io.etcd.jetcd.lock.UnlockResponse;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
final class LockImpl implements Lock {
|
||||
|
||||
private final ClientConnectionManager connectionManager;
|
||||
|
||||
private final LockGrpc.LockFutureStub stub;
|
||||
|
||||
private final ByteSequence namespace;
|
||||
|
||||
LockImpl(ClientConnectionManager connectionManager) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.stub = connectionManager.newStub(LockGrpc::newFutureStub);
|
||||
this.namespace = connectionManager.getNamespace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LockResponse> lock(ByteSequence name, long leaseId) {
|
||||
checkNotNull(name);
|
||||
|
||||
LockRequest request = LockRequest.newBuilder()
|
||||
.setName(Util.prefixNamespace(name.getByteString(), namespace))
|
||||
.setLease(leaseId)
|
||||
.build();
|
||||
|
||||
return connectionManager.execute(
|
||||
() -> stub.lock(request),
|
||||
response -> new LockResponse(response, namespace),
|
||||
Util::isRetryable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UnlockResponse> unlock(ByteSequence lockKey) {
|
||||
checkNotNull(lockKey);
|
||||
|
||||
UnlockRequest request = UnlockRequest.newBuilder()
|
||||
.setKey(Util.prefixNamespace(lockKey.getByteString(), namespace))
|
||||
.build();
|
||||
|
||||
return connectionManager.execute(
|
||||
() -> stub.unlock(request),
|
||||
UnlockResponse::new,
|
||||
Util::isRetryable);
|
||||
}
|
||||
}
|
|
@ -17,9 +17,16 @@
|
|||
package io.etcd.jetcd;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.etcd.jetcd.maintenance.*;
|
||||
import io.etcd.jetcd.maintenance.AlarmMember;
|
||||
import io.etcd.jetcd.maintenance.AlarmResponse;
|
||||
import io.etcd.jetcd.maintenance.DefragmentResponse;
|
||||
import io.etcd.jetcd.maintenance.HashKVResponse;
|
||||
import io.etcd.jetcd.maintenance.MoveLeaderResponse;
|
||||
import io.etcd.jetcd.maintenance.SnapshotResponse;
|
||||
import io.etcd.jetcd.maintenance.StatusResponse;
|
||||
import io.etcd.jetcd.support.CloseableClient;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
|
@ -61,6 +68,16 @@ public interface Maintenance extends CloseableClient {
|
|||
*/
|
||||
CompletableFuture<AlarmResponse> alarmDisarm(AlarmMember member);
|
||||
|
||||
/**
|
||||
* Defragment one member of the cluster by its endpoint.
|
||||
*
|
||||
* @param endpoint the etcd server endpoint.
|
||||
* @return the response result
|
||||
* @deprecated use {@link #defragmentMember(String)}
|
||||
*/
|
||||
@Deprecated
|
||||
CompletableFuture<DefragmentResponse> defragmentMember(URI endpoint);
|
||||
|
||||
/**
|
||||
* Defragment one member of the cluster by its endpoint.
|
||||
*
|
||||
|
@ -83,6 +100,16 @@ public interface Maintenance extends CloseableClient {
|
|||
*/
|
||||
CompletableFuture<DefragmentResponse> defragmentMember(String target);
|
||||
|
||||
/**
|
||||
* get the status of a member by its endpoint.
|
||||
*
|
||||
* @param endpoint the etcd server endpoint.
|
||||
* @return the response result
|
||||
* @deprecated use {@link #statusMember(String)}
|
||||
*/
|
||||
@Deprecated
|
||||
CompletableFuture<StatusResponse> statusMember(URI endpoint);
|
||||
|
||||
/**
|
||||
* get the status of a member by its endpoint.
|
||||
*
|
||||
|
@ -91,6 +118,17 @@ public interface Maintenance extends CloseableClient {
|
|||
*/
|
||||
CompletableFuture<StatusResponse> statusMember(String target);
|
||||
|
||||
/**
|
||||
* returns a hash of the KV state at the time of the RPC.
|
||||
*
|
||||
* @param endpoint the etcd server endpoint.
|
||||
* @param rev the revision
|
||||
* @return the response result
|
||||
* @deprecated use {@link #hashKV(String, long)}
|
||||
*/
|
||||
@Deprecated
|
||||
CompletableFuture<HashKVResponse> hashKV(URI endpoint, long rev);
|
||||
|
||||
/**
|
||||
* returns a hash of the KV state at the time of the RPC.
|
||||
* If revision is zero, the hash is computed on all keys. If the revision
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import io.etcd.jetcd.api.AlarmRequest;
|
||||
import io.etcd.jetcd.api.AlarmType;
|
||||
import io.etcd.jetcd.api.DefragmentRequest;
|
||||
import io.etcd.jetcd.api.HashKVRequest;
|
||||
import io.etcd.jetcd.api.MaintenanceGrpc;
|
||||
import io.etcd.jetcd.api.MoveLeaderRequest;
|
||||
import io.etcd.jetcd.api.SnapshotRequest;
|
||||
import io.etcd.jetcd.api.SnapshotResponse;
|
||||
import io.etcd.jetcd.api.StatusRequest;
|
||||
import io.etcd.jetcd.maintenance.AlarmResponse;
|
||||
import io.etcd.jetcd.maintenance.DefragmentResponse;
|
||||
import io.etcd.jetcd.maintenance.HashKVResponse;
|
||||
import io.etcd.jetcd.maintenance.MoveLeaderResponse;
|
||||
import io.etcd.jetcd.maintenance.StatusResponse;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.toEtcdException;
|
||||
|
||||
/**
|
||||
* Implementation of maintenance client.
|
||||
*/
|
||||
final class MaintenanceImpl implements Maintenance {
|
||||
|
||||
private final ClientConnectionManager connectionManager;
|
||||
private final MaintenanceGrpc.MaintenanceFutureStub stub;
|
||||
private final MaintenanceGrpc.MaintenanceStub streamStub;
|
||||
|
||||
MaintenanceImpl(ClientConnectionManager connectionManager) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.stub = connectionManager.newStub(MaintenanceGrpc::newFutureStub);
|
||||
this.streamStub = connectionManager.newStub(MaintenanceGrpc::newStub);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AlarmResponse> listAlarms() {
|
||||
AlarmRequest alarmRequest = AlarmRequest.newBuilder()
|
||||
.setAlarm(AlarmType.NONE)
|
||||
.setAction(AlarmRequest.AlarmAction.GET)
|
||||
.setMemberID(0)
|
||||
.build();
|
||||
|
||||
return Util.toCompletableFuture(this.stub.alarm(alarmRequest), AlarmResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AlarmResponse> alarmDisarm(io.etcd.jetcd.maintenance.AlarmMember member) {
|
||||
checkArgument(member.getMemberId() != 0, "the member id can not be 0");
|
||||
checkArgument(member.getAlarmType() != io.etcd.jetcd.maintenance.AlarmType.NONE, "alarm type can not be NONE");
|
||||
|
||||
AlarmRequest alarmRequest = AlarmRequest.newBuilder()
|
||||
.setAlarm(AlarmType.NOSPACE)
|
||||
.setAction(AlarmRequest.AlarmAction.DEACTIVATE)
|
||||
.setMemberID(member.getMemberId())
|
||||
.build();
|
||||
|
||||
return Util.toCompletableFuture(this.stub.alarm(alarmRequest), AlarmResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DefragmentResponse> defragmentMember(URI endpoint) {
|
||||
return this.connectionManager.withNewChannel(
|
||||
// TODO
|
||||
endpoint.toString(),
|
||||
MaintenanceGrpc::newFutureStub,
|
||||
stub -> Util.toCompletableFuture(
|
||||
stub.defragment(DefragmentRequest.getDefaultInstance()),
|
||||
DefragmentResponse::new,
|
||||
this.connectionManager.getExecutorService()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DefragmentResponse> defragmentMember(String target) {
|
||||
return this.connectionManager.withNewChannel(
|
||||
target,
|
||||
MaintenanceGrpc::newFutureStub,
|
||||
stub -> Util.toCompletableFuture(
|
||||
stub.defragment(DefragmentRequest.getDefaultInstance()),
|
||||
DefragmentResponse::new,
|
||||
this.connectionManager.getExecutorService()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<StatusResponse> statusMember(URI endpoint) {
|
||||
return this.connectionManager.withNewChannel(
|
||||
// TODO
|
||||
endpoint.toString(),
|
||||
MaintenanceGrpc::newFutureStub,
|
||||
stub -> Util.toCompletableFuture(
|
||||
stub.status(StatusRequest.getDefaultInstance()),
|
||||
StatusResponse::new,
|
||||
this.connectionManager.getExecutorService()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<StatusResponse> statusMember(String target) {
|
||||
return this.connectionManager.withNewChannel(
|
||||
target,
|
||||
MaintenanceGrpc::newFutureStub,
|
||||
stub -> Util.toCompletableFuture(
|
||||
stub.status(StatusRequest.getDefaultInstance()),
|
||||
StatusResponse::new,
|
||||
this.connectionManager.getExecutorService()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<MoveLeaderResponse> moveLeader(long transfereeID) {
|
||||
return Util.toCompletableFuture(
|
||||
this.stub.moveLeader(MoveLeaderRequest.newBuilder().setTargetID(transfereeID).build()),
|
||||
MoveLeaderResponse::new,
|
||||
this.connectionManager.getExecutorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<HashKVResponse> hashKV(URI endpoint, long rev) {
|
||||
return this.connectionManager.withNewChannel(
|
||||
// TODO
|
||||
endpoint.toString(),
|
||||
MaintenanceGrpc::newFutureStub,
|
||||
stub -> Util.toCompletableFuture(
|
||||
stub.hashKV(HashKVRequest.newBuilder().setRevision(rev).build()),
|
||||
HashKVResponse::new,
|
||||
this.connectionManager.getExecutorService()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<HashKVResponse> hashKV(String target, long rev) {
|
||||
return this.connectionManager.withNewChannel(
|
||||
target,
|
||||
MaintenanceGrpc::newFutureStub,
|
||||
stub -> Util.toCompletableFuture(
|
||||
stub.hashKV(HashKVRequest.newBuilder().setRevision(rev).build()),
|
||||
HashKVResponse::new,
|
||||
this.connectionManager.getExecutorService()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Long> snapshot(OutputStream outputStream) {
|
||||
final CompletableFuture<Long> answer = new CompletableFuture<>();
|
||||
final AtomicLong bytes = new AtomicLong(0);
|
||||
|
||||
this.streamStub.snapshot(SnapshotRequest.getDefaultInstance(), new StreamObserver<SnapshotResponse>() {
|
||||
@Override
|
||||
public void onNext(SnapshotResponse snapshotResponse) {
|
||||
try {
|
||||
snapshotResponse.getBlob().writeTo(outputStream);
|
||||
|
||||
bytes.addAndGet(snapshotResponse.getBlob().size());
|
||||
} catch (IOException e) {
|
||||
answer.completeExceptionally(toEtcdException(e));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
answer.completeExceptionally(toEtcdException(throwable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
answer.complete(bytes.get());
|
||||
}
|
||||
});
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void snapshot(StreamObserver<io.etcd.jetcd.maintenance.SnapshotResponse> observer) {
|
||||
this.streamStub.snapshot(SnapshotRequest.getDefaultInstance(), new StreamObserver<SnapshotResponse>() {
|
||||
@Override
|
||||
public void onNext(SnapshotResponse snapshotResponse) {
|
||||
observer.onNext(new io.etcd.jetcd.maintenance.SnapshotResponse(snapshotResponse));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
observer.onError(toEtcdException(throwable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
observer.onCompleted();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016-2023 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd;
|
||||
|
||||
public class Preconditions {
|
||||
|
||||
public static void checkArgument(boolean expression, String errorMessage) {
|
||||
if (!expression) {
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkState(boolean expression, String errorMessage) {
|
||||
if (!expression) {
|
||||
throw new IllegalStateException(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,8 +23,6 @@ public interface Response {
|
|||
|
||||
/**
|
||||
* Returns the response header
|
||||
*
|
||||
* @return the header.
|
||||
*/
|
||||
Header getHeader();
|
||||
|
||||
|
@ -32,29 +30,21 @@ public interface Response {
|
|||
|
||||
/**
|
||||
* Returns the cluster id
|
||||
*
|
||||
* @return the cluster id.
|
||||
*/
|
||||
long getClusterId();
|
||||
|
||||
/**
|
||||
* Returns the member id
|
||||
*
|
||||
* @return the member id.
|
||||
*/
|
||||
long getMemberId();
|
||||
|
||||
/**
|
||||
* Returns the revision id
|
||||
*
|
||||
* @return the revision.
|
||||
*/
|
||||
long getRevision();
|
||||
|
||||
/**
|
||||
* Returns the raft term
|
||||
*
|
||||
* @return theraft term.
|
||||
*/
|
||||
long getRaftTerm();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016-2023 The jetcd authors
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -25,19 +25,20 @@ import io.etcd.jetcd.op.Op;
|
|||
/**
|
||||
* Txn is the interface that wraps mini-transactions.
|
||||
*
|
||||
* <h2>Usage examples</h2>
|
||||
* <h3>Usage examples</h3>
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* txn.If(
|
||||
* new Cmp(KEY, Cmp.Op.GREATER, CmpTarget.value(VALUE)),
|
||||
* new Cmp(KEY, cmp.Op.EQUAL, CmpTarget.version(2))).Then(
|
||||
* Op.put(KEY2, VALUE2, PutOption.DEFAULT),
|
||||
* Op.put(KEY3, VALUE3, PutOption.DEFAULT))
|
||||
* .Else(
|
||||
* Op.put(KEY4, VALUE4, PutOption.DEFAULT),
|
||||
* Op.put(KEY4, VALUE4, PutOption.DEFAULT))
|
||||
* .commit();
|
||||
* new Cmp(KEY, Cmp.Op.GREATER, CmpTarget.value(VALUE)),
|
||||
* new Cmp(KEY, cmp.Op.EQUAL, CmpTarget.version(2))
|
||||
* ).Then(
|
||||
* Op.put(KEY2, VALUE2, PutOption.DEFAULT),
|
||||
* Op.put(KEY3, VALUE3, PutOption.DEFAULT)
|
||||
* ).Else(
|
||||
* Op.put(KEY4, VALUE4, PutOption.DEFAULT),
|
||||
* Op.put(KEY4, VALUE4, PutOption.DEFAULT)
|
||||
* ).commit();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
|
@ -47,17 +48,18 @@ import io.etcd.jetcd.op.Op;
|
|||
* <pre>
|
||||
* {@code
|
||||
* txn.If(
|
||||
* new Cmp(KEY, Cmp.Op.GREATER, CmpTarget.value(VALUE))).If(
|
||||
* new Cmp(KEY, cmp.Op.EQUAL, CmpTarget.version(VERSION)))
|
||||
* .Then(
|
||||
* Op.put(KEY2, VALUE2, PutOption.DEFAULT))
|
||||
* .Then(
|
||||
* Op.put(KEY3, VALUE3, PutOption.DEFAULT))
|
||||
* .Else(
|
||||
* Op.put(KEY4, VALUE4, PutOption.DEFAULT))
|
||||
* .Else(
|
||||
* Op.put(KEY4, VALUE4, PutOption.DEFAULT))
|
||||
* .commit();
|
||||
* new Cmp(KEY, Cmp.Op.GREATER, CmpTarget.value(VALUE))
|
||||
* ).If(
|
||||
* new Cmp(KEY, cmp.Op.EQUAL, CmpTarget.version(VERSION))
|
||||
* ).Then(
|
||||
* Op.put(KEY2, VALUE2, PutOption.DEFAULT)
|
||||
* ).Then(
|
||||
* Op.put(KEY3, VALUE3, PutOption.DEFAULT)
|
||||
* ).Else(
|
||||
* Op.put(KEY4, VALUE4, PutOption.DEFAULT)
|
||||
* ).Else(
|
||||
* Op.put(KEY4, VALUE4, PutOption.DEFAULT)
|
||||
* ).commit();
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
|
|
|
@ -14,23 +14,26 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.support;
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.Constants;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.stub.AbstractStub;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.toEtcdException;
|
||||
import static io.grpc.stub.MetadataUtils.newAttachHeadersInterceptor;
|
||||
|
||||
public final class Util {
|
||||
|
@ -48,38 +51,48 @@ public final class Util {
|
|||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static ByteString prefixNamespace(ByteSequence key, ByteSequence namespace) {
|
||||
return ByteString.copyFrom(namespace.isEmpty() ? key.getBytes() : namespace.concat(key).getBytes());
|
||||
/**
|
||||
* convert ListenableFuture of Type S to CompletableFuture of Type T.
|
||||
*/
|
||||
static <S, T> CompletableFuture<T> toCompletableFuture(ListenableFuture<S> sourceFuture, Function<S, T> resultConvert,
|
||||
Executor executor) {
|
||||
|
||||
CompletableFuture<T> targetFuture = new CompletableFuture<T>() {
|
||||
// the cancel of targetFuture also cancels the sourceFuture.
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
super.cancel(mayInterruptIfRunning);
|
||||
return sourceFuture.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
};
|
||||
|
||||
sourceFuture.addListener(() -> {
|
||||
try {
|
||||
targetFuture.complete(resultConvert.apply(sourceFuture.get()));
|
||||
} catch (Exception e) {
|
||||
targetFuture.completeExceptionally(toEtcdException(e));
|
||||
}
|
||||
}, executor);
|
||||
|
||||
return targetFuture;
|
||||
}
|
||||
|
||||
public static boolean isRetryable(Throwable e) {
|
||||
Status status = Status.fromThrowable(e);
|
||||
return Status.UNAVAILABLE.getCode().equals(status.getCode()) || isInvalidTokenError(status);
|
||||
}
|
||||
|
||||
public static boolean isInvalidTokenError(Status status) {
|
||||
return status.getCode() == Status.Code.UNAUTHENTICATED
|
||||
&& "etcdserver: invalid auth token".equals(status.getDescription());
|
||||
}
|
||||
|
||||
public static <T> T supplyIfNull(T target, Supplier<T> supplier) {
|
||||
return target != null ? target : supplier.get();
|
||||
}
|
||||
|
||||
public static ByteString prefixNamespace(ByteString key, ByteSequence namespace) {
|
||||
return namespace.isEmpty() ? key : ByteString.copyFrom(namespace.concat(key).getBytes());
|
||||
}
|
||||
|
||||
public static ByteString prefixNamespaceToRangeEnd(ByteSequence end, ByteSequence namespace) {
|
||||
if (namespace.isEmpty()) {
|
||||
return ByteString.copyFrom(end.getBytes());
|
||||
}
|
||||
|
||||
if (end.size() == 1 && end.getBytes()[0] == 0) {
|
||||
// range end is '\0', calculate the prefixed range end by (key + 1)
|
||||
byte[] prefixedEndArray = namespace.getBytes();
|
||||
boolean ok = false;
|
||||
for (int i = prefixedEndArray.length - 1; i >= 0; i--) {
|
||||
prefixedEndArray[i] = (byte) (prefixedEndArray[i] + 1);
|
||||
if (prefixedEndArray[i] != 0) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
// 0xff..ff => 0x00
|
||||
prefixedEndArray = Constants.NULL_KEY.getBytes();
|
||||
}
|
||||
return ByteString.copyFrom(prefixedEndArray);
|
||||
} else {
|
||||
return ByteString.copyFrom(namespace.concat(end).getBytes());
|
||||
}
|
||||
return namespace.isEmpty() ? key : namespace.getByteString().concat(key);
|
||||
}
|
||||
|
||||
public static ByteString prefixNamespaceToRangeEnd(ByteString end, ByteSequence namespace) {
|
||||
|
@ -89,7 +102,7 @@ public final class Util {
|
|||
|
||||
if (end.size() == 1 && end.toByteArray()[0] == 0) {
|
||||
// range end is '\0', calculate the prefixed range end by (key + 1)
|
||||
byte[] prefixedEndArray = namespace.getBytes();
|
||||
byte[] prefixedEndArray = namespace.getByteString().toByteArray();
|
||||
boolean ok = false;
|
||||
for (int i = prefixedEndArray.length - 1; i >= 0; i--) {
|
||||
prefixedEndArray[i] = (byte) (prefixedEndArray[i] + 1);
|
||||
|
@ -100,11 +113,11 @@ public final class Util {
|
|||
}
|
||||
if (!ok) {
|
||||
// 0xff..ff => 0x00
|
||||
prefixedEndArray = Constants.NULL_KEY.getBytes();
|
||||
prefixedEndArray = new byte[] { 0 };
|
||||
}
|
||||
return ByteString.copyFrom(prefixedEndArray);
|
||||
} else {
|
||||
return ByteString.copyFrom(namespace.concat(end).getBytes());
|
||||
return namespace.getByteString().concat(end);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +125,7 @@ public final class Util {
|
|||
return namespace.isEmpty() ? key : key.substring(namespace.size());
|
||||
}
|
||||
|
||||
public static <T extends AbstractStub<T>> T applyRequireLeader(boolean requireLeader, T stub) {
|
||||
static <T extends AbstractStub<T>> T applyRequireLeader(boolean requireLeader, T stub) {
|
||||
if (!requireLeader) {
|
||||
return stub;
|
||||
}
|
||||
|
@ -121,15 +134,13 @@ public final class Util {
|
|||
return stub.withInterceptors(newAttachHeadersInterceptor(md));
|
||||
}
|
||||
|
||||
public static ThreadFactory createThreadFactory(String prefix, boolean daemon) {
|
||||
ThreadFactory backingThreadFactory = Executors.defaultThreadFactory();
|
||||
public static boolean isHaltError(final Status status) {
|
||||
return status.getCode() != Status.Code.UNAVAILABLE && status.getCode() != Status.Code.INTERNAL;
|
||||
}
|
||||
|
||||
return r -> {
|
||||
Thread t = backingThreadFactory.newThread(r);
|
||||
t.setDaemon(daemon);
|
||||
// set a proper name so it is easier to find out the where the thread was created
|
||||
t.setName(prefix + t.getName());
|
||||
return t;
|
||||
};
|
||||
static final String NO_LEADER_ERROR_MESSAGE = "etcdserver: no leader";
|
||||
|
||||
public static boolean isNoLeaderError(final Status status) {
|
||||
return status.getCode() == Status.Code.UNAVAILABLE && NO_LEADER_ERROR_MESSAGE.equals(status.getDescription());
|
||||
}
|
||||
}
|
|
@ -226,11 +226,6 @@ public interface Watch extends CloseableClient {
|
|||
@Override
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Returns if watcher is already closed
|
||||
*/
|
||||
boolean isClosed();
|
||||
|
||||
/**
|
||||
* Requests the latest revision processed and propagates it to listeners
|
||||
*/
|
||||
|
|
|
@ -14,23 +14,20 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.impl;
|
||||
package io.etcd.jetcd;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.Watch;
|
||||
import io.etcd.jetcd.api.VertxWatchGrpc;
|
||||
import io.etcd.jetcd.api.WatchCancelRequest;
|
||||
import io.etcd.jetcd.api.WatchCreateRequest;
|
||||
import io.etcd.jetcd.api.WatchGrpc;
|
||||
import io.etcd.jetcd.api.WatchProgressRequest;
|
||||
import io.etcd.jetcd.api.WatchRequest;
|
||||
import io.etcd.jetcd.api.WatchResponse;
|
||||
|
@ -38,10 +35,8 @@ import io.etcd.jetcd.common.exception.ErrorCode;
|
|||
import io.etcd.jetcd.common.exception.EtcdException;
|
||||
import io.etcd.jetcd.options.OptionsUtil;
|
||||
import io.etcd.jetcd.options.WatchOption;
|
||||
import io.etcd.jetcd.support.Errors;
|
||||
import io.etcd.jetcd.support.Util;
|
||||
import io.grpc.Status;
|
||||
import io.vertx.core.streams.WriteStream;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
|
@ -57,24 +52,22 @@ import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.toEtcdExceptio
|
|||
/**
|
||||
* watch Implementation.
|
||||
*/
|
||||
final class WatchImpl extends Impl implements Watch {
|
||||
final class WatchImpl implements Watch {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WatchImpl.class);
|
||||
|
||||
private final Object lock;
|
||||
private final VertxWatchGrpc.WatchVertxStub stub;
|
||||
private final ClientConnectionManager connectionManager;
|
||||
private final WatchGrpc.WatchStub stub;
|
||||
private final ListeningScheduledExecutorService executor;
|
||||
private final AtomicBoolean closed;
|
||||
private final List<WatcherImpl> watchers;
|
||||
private final ByteSequence namespace;
|
||||
|
||||
WatchImpl(ClientConnectionManager connectionManager) {
|
||||
super(connectionManager);
|
||||
|
||||
this.lock = new Object();
|
||||
this.stub = connectionManager.newStub(VertxWatchGrpc::newVertxStub);
|
||||
// set it to daemon as there is no way for users to create this thread pool by their own
|
||||
this.executor = MoreExecutors.listeningDecorator(
|
||||
Executors.newScheduledThreadPool(1, Util.createThreadFactory("jetcd-watch-", true)));
|
||||
this.connectionManager = connectionManager;
|
||||
this.stub = connectionManager.newStub(WatchGrpc::newStub);
|
||||
this.executor = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(1));
|
||||
this.closed = new AtomicBoolean();
|
||||
this.watchers = new CopyOnWriteArrayList<>();
|
||||
this.namespace = connectionManager.getNamespace();
|
||||
|
@ -117,14 +110,13 @@ final class WatchImpl extends Impl implements Watch {
|
|||
}
|
||||
}
|
||||
|
||||
final class WatcherImpl implements Watcher {
|
||||
final class WatcherImpl implements Watcher, StreamObserver<WatchResponse> {
|
||||
private final ByteSequence key;
|
||||
private final WatchOption option;
|
||||
private final Listener listener;
|
||||
private final AtomicBoolean closed;
|
||||
|
||||
private final AtomicReference<WriteStream<WatchRequest>> wstream;
|
||||
private final AtomicBoolean started;
|
||||
private StreamObserver<WatchRequest> stream;
|
||||
private long revision;
|
||||
private long id;
|
||||
|
||||
|
@ -134,8 +126,7 @@ final class WatchImpl extends Impl implements Watch {
|
|||
this.listener = listener;
|
||||
this.closed = new AtomicBoolean();
|
||||
|
||||
this.started = new AtomicBoolean();
|
||||
this.wstream = new AtomicReference<>();
|
||||
this.stream = null;
|
||||
this.id = -1;
|
||||
this.revision = this.option.getRevision();
|
||||
}
|
||||
|
@ -146,8 +137,7 @@ final class WatchImpl extends Impl implements Watch {
|
|||
//
|
||||
// ************************
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
boolean isClosed() {
|
||||
return this.closed.get() || WatchImpl.this.closed.get();
|
||||
}
|
||||
|
||||
|
@ -156,22 +146,20 @@ final class WatchImpl extends Impl implements Watch {
|
|||
return;
|
||||
}
|
||||
|
||||
if (started.compareAndSet(false, true)) {
|
||||
// id is not really useful today, but it may be in etcd 3.4
|
||||
if (stream == null) {
|
||||
// id is not really useful today but it may be in etcd 3.4
|
||||
id = -1;
|
||||
|
||||
WatchCreateRequest.Builder builder = WatchCreateRequest.newBuilder()
|
||||
.setKey(Util.prefixNamespace(this.key, namespace))
|
||||
.setPrevKv(this.option.isPrevKV())
|
||||
.setKey(Util.prefixNamespace(this.key.getByteString(), namespace)).setPrevKv(this.option.isPrevKV())
|
||||
.setProgressNotify(option.isProgressNotify()).setStartRevision(this.revision);
|
||||
|
||||
option.getEndKey()
|
||||
.map(endKey -> Util.prefixNamespaceToRangeEnd(endKey, namespace))
|
||||
option.getEndKey().map(endKey -> Util.prefixNamespaceToRangeEnd(endKey.getByteString(), namespace))
|
||||
.ifPresent(builder::setRangeEnd);
|
||||
|
||||
if (option.getEndKey().isEmpty() && option.isPrefix()) {
|
||||
if (!option.getEndKey().isPresent() && option.isPrefix()) {
|
||||
ByteSequence endKey = OptionsUtil.prefixEndOf(key);
|
||||
builder.setRangeEnd(Util.prefixNamespaceToRangeEnd(endKey, namespace));
|
||||
builder.setRangeEnd(Util.prefixNamespaceToRangeEnd(endKey.getByteString(), namespace));
|
||||
}
|
||||
|
||||
if (option.isNoDelete()) {
|
||||
|
@ -182,15 +170,8 @@ final class WatchImpl extends Impl implements Watch {
|
|||
builder.addFilters(WatchCreateRequest.FilterType.NOPUT);
|
||||
}
|
||||
|
||||
var ignored = Util.applyRequireLeader(option.withRequireLeader(), stub)
|
||||
.watchWithHandler(
|
||||
stream -> {
|
||||
wstream.set(stream);
|
||||
stream.write(WatchRequest.newBuilder().setCreateRequest(builder).build());
|
||||
},
|
||||
this::onNext,
|
||||
event -> onCompleted(),
|
||||
this::onError);
|
||||
stream = Util.applyRequireLeader(option.withRequireLeader(), stub).watch(this);
|
||||
stream.onNext(WatchRequest.newBuilder().setCreateRequest(builder).build());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,15 +181,16 @@ final class WatchImpl extends Impl implements Watch {
|
|||
// sync with onError()
|
||||
synchronized (WatchImpl.this.lock) {
|
||||
if (closed.compareAndSet(false, true)) {
|
||||
if (wstream.get() != null) {
|
||||
if (id != -1) {
|
||||
final WatchCancelRequest watchCancelRequest = WatchCancelRequest.newBuilder().setWatchId(this.id)
|
||||
.build();
|
||||
final WatchRequest request = WatchRequest.newBuilder().setCancelRequest(watchCancelRequest).build();
|
||||
if (stream != null) {
|
||||
WatchCancelRequest watchCancelRequest = WatchCancelRequest.newBuilder().setWatchId(this.id).build();
|
||||
|
||||
wstream.get().end(request);
|
||||
} else {
|
||||
wstream.get().end();
|
||||
if (id != -1) {
|
||||
stream.onNext(WatchRequest.newBuilder().setCancelRequest(watchCancelRequest).build());
|
||||
}
|
||||
|
||||
if (stream != null) {
|
||||
stream.onError(Status.CANCELLED.withDescription("shutdown").asException());
|
||||
stream = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,9 +206,9 @@ final class WatchImpl extends Impl implements Watch {
|
|||
|
||||
@Override
|
||||
public void requestProgress() {
|
||||
if (!closed.get() && wstream.get() != null) {
|
||||
if (!closed.get() && stream != null) {
|
||||
WatchProgressRequest watchProgressRequest = WatchProgressRequest.newBuilder().build();
|
||||
wstream.get().write(WatchRequest.newBuilder().setProgressRequest(watchProgressRequest).build());
|
||||
stream.onNext(WatchRequest.newBuilder().setProgressRequest(watchProgressRequest).build());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,7 +218,8 @@ final class WatchImpl extends Impl implements Watch {
|
|||
//
|
||||
// ************************
|
||||
|
||||
private void onNext(WatchResponse response) {
|
||||
@Override
|
||||
public void onNext(WatchResponse response) {
|
||||
if (closed.get()) {
|
||||
// events eventually received when the client is closed should
|
||||
// not be propagated to the listener
|
||||
|
@ -245,11 +228,9 @@ final class WatchImpl extends Impl implements Watch {
|
|||
|
||||
// handle a special case when watch has been created and closed at the same time
|
||||
if (response.getCreated() && response.getCanceled() && response.getCancelReason() != null
|
||||
&& (response.getCancelReason().contains("etcdserver: permission denied") ||
|
||||
response.getCancelReason().contains("etcdserver: invalid auth token"))) {
|
||||
|
||||
&& response.getCancelReason().contains("etcdserver: permission denied")) {
|
||||
// potentially access token expired
|
||||
connectionManager().authCredential().refresh();
|
||||
connectionManager.authInterceptor().refresh();
|
||||
Status error = Status.Code.CANCELLED.toStatus().withDescription(response.getCancelReason());
|
||||
handleError(toEtcdException(error), true);
|
||||
} else if (response.getCreated()) {
|
||||
|
@ -265,9 +246,6 @@ final class WatchImpl extends Impl implements Watch {
|
|||
|
||||
revision = Math.max(revision, response.getHeader().getRevision());
|
||||
id = response.getWatchId();
|
||||
if (option.isCreatedNotify()) {
|
||||
listener.onNext(new io.etcd.jetcd.watch.WatchResponse(response));
|
||||
}
|
||||
} else if (response.getCanceled()) {
|
||||
|
||||
//
|
||||
|
@ -315,11 +293,8 @@ final class WatchImpl extends Impl implements Watch {
|
|||
}
|
||||
}
|
||||
|
||||
private void onCompleted() {
|
||||
listener.onCompleted();
|
||||
}
|
||||
|
||||
private void onError(Throwable t) {
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
handleError(toEtcdException(t), shouldReschedule(Status.fromThrowable(t)));
|
||||
}
|
||||
|
||||
|
@ -331,18 +306,12 @@ final class WatchImpl extends Impl implements Watch {
|
|||
}
|
||||
|
||||
listener.onError(etcdException);
|
||||
if (wstream.get() != null) {
|
||||
wstream.get().end();
|
||||
if (stream != null) {
|
||||
stream.onCompleted();
|
||||
}
|
||||
wstream.set(null);
|
||||
started.set(false);
|
||||
stream = null;
|
||||
}
|
||||
if (shouldReschedule) {
|
||||
if (etcdException.getMessage().contains("etcdserver: permission denied")) {
|
||||
// potentially access token expired
|
||||
connectionManager().authCredential().refresh();
|
||||
}
|
||||
|
||||
reschedule();
|
||||
return;
|
||||
}
|
||||
|
@ -350,14 +319,14 @@ final class WatchImpl extends Impl implements Watch {
|
|||
}
|
||||
|
||||
private boolean shouldReschedule(final Status status) {
|
||||
return !Errors.isHaltError(status) && !Errors.isNoLeaderError(status);
|
||||
return !Util.isHaltError(status) && !Util.isNoLeaderError(status);
|
||||
}
|
||||
|
||||
private void reschedule() {
|
||||
Futures.addCallback(executor.schedule(this::resume, 500, TimeUnit.MILLISECONDS), new FutureCallback<Object>() {
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
LOG.warn("scheduled resume failed", t);
|
||||
public void onFailure(Throwable throwable) {
|
||||
LOG.error("scheduled resume failed", throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -365,5 +334,9 @@ final class WatchImpl extends Impl implements Watch {
|
|||
}
|
||||
}, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,11 +16,10 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthDisableResponse returned by {@link Auth#authDisable()} contains a header.
|
||||
* AuthDisableResponse returned by {@link io.etcd.jetcd.Auth#authDisable()} contains a header.
|
||||
*/
|
||||
public class AuthDisableResponse extends AbstractResponse<io.etcd.jetcd.api.AuthDisableResponse> {
|
||||
|
||||
|
|
|
@ -16,11 +16,10 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthEnableResponse returned by {@link Auth#authEnable()} call contains a header.
|
||||
* AuthEnableResponse returned by {@link io.etcd.jetcd.Auth#authEnable()} call contains a header.
|
||||
*/
|
||||
public class AuthEnableResponse extends AbstractResponse<io.etcd.jetcd.api.AuthEnableResponse> {
|
||||
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import io.etcd.jetcd.ClientBuilder;
|
||||
import io.etcd.jetcd.api.AuthGrpc;
|
||||
import io.etcd.jetcd.api.AuthenticateRequest;
|
||||
import io.etcd.jetcd.api.AuthenticateResponse;
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.ClientCall;
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.ForwardingClientCall;
|
||||
import io.grpc.ForwardingClientCallListener;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.stub.MetadataUtils;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static io.etcd.jetcd.Util.isInvalidTokenError;
|
||||
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.handleInterrupt;
|
||||
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.toEtcdException;
|
||||
|
||||
/**
|
||||
* AuthTokenInterceptor fills header with Auth token of any rpc calls and
|
||||
* refreshes token if the rpc results an invalid Auth token error.
|
||||
*/
|
||||
public class AuthInterceptor implements ClientInterceptor {
|
||||
private static final Metadata.Key<String> TOKEN = Metadata.Key.of("token", Metadata.ASCII_STRING_MARSHALLER);
|
||||
|
||||
private final Object lock;
|
||||
private final ClientBuilder builder;
|
||||
private final ClientInterceptor[] interceptors;
|
||||
private volatile String token;
|
||||
|
||||
public AuthInterceptor(ClientBuilder builder) {
|
||||
this.lock = new Object();
|
||||
this.builder = builder;
|
||||
|
||||
List<ClientInterceptor> interceptorsChain = new ArrayList<>();
|
||||
if (builder.authHeaders() != null) {
|
||||
Metadata metadata = new Metadata();
|
||||
builder.authHeaders().forEach((BiConsumer<Metadata.Key, Object>) metadata::put);
|
||||
|
||||
interceptorsChain.add(MetadataUtils.newAttachHeadersInterceptor(metadata));
|
||||
}
|
||||
if (builder.authInterceptors() != null) {
|
||||
interceptorsChain.addAll(builder.authInterceptors());
|
||||
}
|
||||
|
||||
this.interceptors = interceptorsChain.isEmpty() ? null : interceptorsChain.toArray(new ClientInterceptor[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
|
||||
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
|
||||
|
||||
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
|
||||
@Override
|
||||
public void start(Listener<RespT> responseListener, Metadata headers) {
|
||||
String token = getToken(next);
|
||||
if (token != null) {
|
||||
headers.put(TOKEN, token);
|
||||
}
|
||||
super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
|
||||
@Override
|
||||
public void onClose(Status status, Metadata trailers) {
|
||||
if (isInvalidTokenError(status)) {
|
||||
try {
|
||||
refreshToken(next);
|
||||
} catch (Exception e) {
|
||||
// don't throw any error here.
|
||||
// rpc will retry on expired auth token.
|
||||
}
|
||||
}
|
||||
super.onClose(status, trailers);
|
||||
}
|
||||
}, headers);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
synchronized (lock) {
|
||||
token = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get token from etcd with name and password.
|
||||
*
|
||||
* @param channel channel to etcd
|
||||
* @return authResp
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private ListenableFuture<AuthenticateResponse> authenticate(@Nonnull Channel channel) {
|
||||
|
||||
final ByteString user = ByteString.copyFrom(builder.user().getBytes());
|
||||
final ByteString pass = ByteString.copyFrom(builder.password().getBytes());
|
||||
|
||||
checkArgument(!user.isEmpty(), "username can not be empty.");
|
||||
checkArgument(!pass.isEmpty(), "password can not be empty.");
|
||||
|
||||
AuthGrpc.AuthFutureStub authFutureStub = AuthGrpc.newFutureStub(channel);
|
||||
if (interceptors != null) {
|
||||
authFutureStub = authFutureStub.withInterceptors(interceptors);
|
||||
}
|
||||
|
||||
return authFutureStub.authenticate(
|
||||
AuthenticateRequest.newBuilder()
|
||||
.setNameBytes(user)
|
||||
.setPasswordBytes(pass)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getToken(Channel channel) {
|
||||
if (token == null) {
|
||||
synchronized (lock) {
|
||||
if (token == null) {
|
||||
token = generateToken(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private void refreshToken(Channel channel) {
|
||||
synchronized (lock) {
|
||||
token = generateToken(channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get token with ClientBuilder.
|
||||
*
|
||||
* @return the auth token
|
||||
* @throws io.etcd.jetcd.common.exception.EtcdException a exception indicates failure reason.
|
||||
*/
|
||||
@Nullable
|
||||
private String generateToken(Channel channel) {
|
||||
if (builder.user() != null && builder.password() != null) {
|
||||
try {
|
||||
return authenticate(channel).get().getToken();
|
||||
} catch (InterruptedException ite) {
|
||||
throw handleInterrupt(ite);
|
||||
} catch (ExecutionException exee) {
|
||||
throw toEtcdException(exee);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -16,12 +16,11 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthRoleAddResponse returned by {@link Auth#roleAdd(ByteSequence)} contains
|
||||
* AuthRoleAddResponse returned by {@link io.etcd.jetcd.Auth#roleAdd(ByteSequence)} contains
|
||||
* a header.
|
||||
*/
|
||||
public class AuthRoleAddResponse extends AbstractResponse<io.etcd.jetcd.api.AuthRoleAddResponse> {
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthRoleDeleteResponse returned by {@link Auth#roleDelete(ByteSequence)}
|
||||
* AuthRoleDeleteResponse returned by {@link io.etcd.jetcd.Auth#roleDelete(ByteSequence)}
|
||||
* contains a header.
|
||||
*/
|
||||
public class AuthRoleDeleteResponse extends AbstractResponse<io.etcd.jetcd.api.AuthRoleDeleteResponse> {
|
||||
|
|
|
@ -19,12 +19,11 @@ package io.etcd.jetcd.auth;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthRoleGetResponse returned by {@link Auth#roleGet(ByteSequence)} contains
|
||||
* AuthRoleGetResponse returned by {@link io.etcd.jetcd.Auth#roleGet(ByteSequence)} contains
|
||||
* a header and a list of permissions.
|
||||
*/
|
||||
public class AuthRoleGetResponse extends AbstractResponse<io.etcd.jetcd.api.AuthRoleGetResponse> {
|
||||
|
|
|
@ -16,13 +16,12 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthRoleGrantPermissionResponse returned by
|
||||
* {@link Auth#roleGrantPermission(ByteSequence, ByteSequence, ByteSequence, Permission.Type)} contains a
|
||||
* {@link io.etcd.jetcd.Auth#roleGrantPermission(ByteSequence, ByteSequence, ByteSequence, Permission.Type)} contains a
|
||||
* header.
|
||||
*/
|
||||
public class AuthRoleGrantPermissionResponse extends AbstractResponse<io.etcd.jetcd.api.AuthRoleGrantPermissionResponse> {
|
||||
|
|
|
@ -18,8 +18,8 @@ package io.etcd.jetcd.auth;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthRoleListResponse returned by {@link Auth#roleList()} contains a header and
|
||||
|
@ -33,8 +33,6 @@ public class AuthRoleListResponse extends AbstractResponse<io.etcd.jetcd.api.Aut
|
|||
|
||||
/**
|
||||
* Returns a list of roles.
|
||||
*
|
||||
* @return the roles.
|
||||
*/
|
||||
public List<String> getRoles() {
|
||||
return getResponse().getRolesList();
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthRoleRevokePermissionResponse
|
||||
* AuthRoleRevokePermissionResponse returned by
|
||||
* {@link io.etcd.jetcd.Auth#roleRevokePermission(io.etcd.jetcd.ByteSequence.ByteSequence,
|
||||
* io.etcd.jetcd.ByteSequence.ByteSequence, io.etcd.jetcd.ByteSequence.ByteSequence)} contains a header.
|
||||
*/
|
||||
public class AuthRoleRevokePermissionResponse extends AbstractResponse<io.etcd.jetcd.api.AuthRoleRevokePermissionResponse> {
|
||||
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthUserAddResponse returned by {@link Auth#userAdd(ByteSequence, ByteSequence)} contains a
|
||||
* AuthUserAddResponse returned by {@link io.etcd.jetcd.Auth#userAdd(ByteSequence, ByteSequence)} contains a
|
||||
* header.
|
||||
*/
|
||||
public class AuthUserAddResponse extends AbstractResponse<io.etcd.jetcd.api.AuthUserAddResponse> {
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthUserChangePasswordResponse
|
||||
* AuthUserChangePasswordResponse returned by
|
||||
* {@link io.etcd.jetcd.Auth#userChangePassword(io.etcd.jetcd.ByteSequence.ByteSequence,
|
||||
* io.etcd.jetcd.ByteSequence.ByteSequence)} contains a header.
|
||||
*/
|
||||
public class AuthUserChangePasswordResponse extends AbstractResponse<io.etcd.jetcd.api.AuthUserChangePasswordResponse> {
|
||||
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthUserDeleteResponse returned by {@link Auth#userDelete(ByteSequence)} contains a header.
|
||||
* AuthUserDeleteResponse returned by {@link io.etcd.jetcd.Auth#userDelete(ByteSequence)} contains a header.
|
||||
*/
|
||||
public class AuthUserDeleteResponse extends AbstractResponse<io.etcd.jetcd.api.AuthUserDeleteResponse> {
|
||||
|
||||
|
|
|
@ -18,12 +18,11 @@ package io.etcd.jetcd.auth;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthUserGetResponse returned by {@link Auth#userGet(ByteSequence)} contains a header and
|
||||
* AuthUserGetResponse returned by {@link io.etcd.jetcd.Auth#userGet(ByteSequence)} contains a header and
|
||||
* a list of roles associated with the user.
|
||||
*/
|
||||
public class AuthUserGetResponse extends AbstractResponse<io.etcd.jetcd.api.AuthUserGetResponse> {
|
||||
|
@ -34,8 +33,6 @@ public class AuthUserGetResponse extends AbstractResponse<io.etcd.jetcd.api.Auth
|
|||
|
||||
/**
|
||||
* Returns a list of roles.
|
||||
*
|
||||
* @return the roles.
|
||||
*/
|
||||
public List<String> getRoles() {
|
||||
return getResponse().getRolesList();
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthUserGrantRoleResponse
|
||||
* AuthUserGrantRoleResponse returned by
|
||||
* {@link io.etcd.jetcd.Auth#userGrantRole(io.etcd.jetcd.ByteSequence.ByteSequence,
|
||||
* io.etcd.jetcd.ByteSequence.ByteSequence)} contains a header.
|
||||
*/
|
||||
public class AuthUserGrantRoleResponse extends AbstractResponse<io.etcd.jetcd.api.AuthUserGrantRoleResponse> {
|
||||
|
||||
|
|
|
@ -18,11 +18,10 @@ package io.etcd.jetcd.auth;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthUserListResponse returned by {@link Auth#userList()} contains a header and
|
||||
* AuthUserListResponse returned by {@link io.etcd.jetcd.Auth#userList()} contains a header and
|
||||
* a list of users.
|
||||
*/
|
||||
public class AuthUserListResponse extends AbstractResponse<io.etcd.jetcd.api.AuthUserListResponse> {
|
||||
|
@ -33,8 +32,6 @@ public class AuthUserListResponse extends AbstractResponse<io.etcd.jetcd.api.Aut
|
|||
|
||||
/**
|
||||
* Returns a list of users.
|
||||
*
|
||||
* @return the users.
|
||||
*/
|
||||
public List<String> getUsers() {
|
||||
return getResponse().getUsersList();
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
|
||||
package io.etcd.jetcd.auth;
|
||||
|
||||
import io.etcd.jetcd.Auth;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
/**
|
||||
* AuthUserRevokeRoleResponse returned by {@link Auth#userRevokeRole(ByteSequence, ByteSequence)}
|
||||
* AuthUserRevokeRoleResponse returned by {@link io.etcd.jetcd.Auth#userRevokeRole(ByteSequence, ByteSequence)}
|
||||
* contains a header.
|
||||
*/
|
||||
public class AuthUserRevokeRoleResponse extends AbstractResponse<io.etcd.jetcd.api.AuthUserRevokeRoleResponse> {
|
||||
|
|
|
@ -39,8 +39,6 @@ public class Permission {
|
|||
|
||||
/**
|
||||
* Returns the type of Permission: READ, WRITE, READWRITE, or UNRECOGNIZED.
|
||||
*
|
||||
* @return the permission type.
|
||||
*/
|
||||
public Type getPermType() {
|
||||
return permType;
|
||||
|
|
|
@ -19,7 +19,7 @@ package io.etcd.jetcd.cluster;
|
|||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import io.etcd.jetcd.support.Util;
|
||||
import io.etcd.jetcd.Util;
|
||||
|
||||
public class Member {
|
||||
|
||||
|
@ -31,8 +31,6 @@ public class Member {
|
|||
|
||||
/**
|
||||
* Returns the member ID for this member.
|
||||
*
|
||||
* @return the id.
|
||||
*/
|
||||
public long getId() {
|
||||
return member.getID();
|
||||
|
@ -40,8 +38,6 @@ public class Member {
|
|||
|
||||
/**
|
||||
* Returns the human-readable name of the member, ff the member is not started, the name will be an empty string.
|
||||
*
|
||||
* @return the name.
|
||||
*/
|
||||
public String getName() {
|
||||
return member.getName();
|
||||
|
@ -49,8 +45,6 @@ public class Member {
|
|||
|
||||
/**
|
||||
* Returns the list of URLs the member exposes to the cluster for communication.
|
||||
*
|
||||
* @return the peer url
|
||||
*/
|
||||
public List<URI> getPeerURIs() {
|
||||
return Util.toURIs(member.getPeerURLsList());
|
||||
|
@ -59,19 +53,8 @@ public class Member {
|
|||
/**
|
||||
* Returns list of URLs the member exposes to clients for communication, if the member is not started, clientURLs will
|
||||
* be empty.
|
||||
*
|
||||
* @return the client URIs.
|
||||
*/
|
||||
public List<URI> getClientURIs() {
|
||||
return Util.toURIs(member.getClientURLsList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the member is raft learner
|
||||
*
|
||||
* @return if the member is raft learner
|
||||
*/
|
||||
public boolean isLearner() {
|
||||
return member.getIsLearner();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,10 @@ package io.etcd.jetcd.cluster;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import io.etcd.jetcd.Cluster;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
/**
|
||||
* MemberAddResponse returned by {@link Cluster#addMember(List, boolean)}
|
||||
* MemberAddResponse returned by {@link io.etcd.jetcd.Cluster#addMember(List)}
|
||||
* contains a header, added member, and list of members after adding the new member.
|
||||
*/
|
||||
public class MemberAddResponse extends AbstractResponse<io.etcd.jetcd.api.MemberAddResponse> {
|
||||
|
|
|
@ -18,8 +18,8 @@ package io.etcd.jetcd.cluster;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.Cluster;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
/**
|
||||
* MemberListResponse returned by {@link Cluster#listMember()}
|
||||
|
|
|
@ -18,11 +18,10 @@ package io.etcd.jetcd.cluster;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import io.etcd.jetcd.Cluster;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
/**
|
||||
* MemberRemoveResponse returned by {@link Cluster#removeMember(long)}
|
||||
* MemberRemoveResponse returned by {@link io.etcd.jetcd.Cluster#removeMember(long)}
|
||||
* contains a header and a list of member the removal of the member.
|
||||
*/
|
||||
public class MemberRemoveResponse extends AbstractResponse<io.etcd.jetcd.api.MemberRemoveResponse> {
|
||||
|
|
|
@ -18,11 +18,10 @@ package io.etcd.jetcd.cluster;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import io.etcd.jetcd.Cluster;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
/**
|
||||
* MemberUpdateResponse returned by {@link Cluster#updateMember(long, List)}
|
||||
* MemberUpdateResponse returned by {@link io.etcd.jetcd.Cluster#updateMember(long, List)}
|
||||
* contains a header and a list of members after the member update.
|
||||
*/
|
||||
public class MemberUpdateResponse extends AbstractResponse<io.etcd.jetcd.api.MemberUpdateResponse> {
|
||||
|
|
|
@ -22,9 +22,7 @@ import java.util.stream.Collectors;
|
|||
/**
|
||||
* Util class for Cluster models.
|
||||
*/
|
||||
final class Util {
|
||||
private Util() {
|
||||
}
|
||||
public class Util {
|
||||
|
||||
/**
|
||||
* Converts a list of API member to a List of client side member.
|
||||
|
|
|
@ -16,26 +16,22 @@
|
|||
|
||||
package io.etcd.jetcd.election;
|
||||
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
public class CampaignResponse extends AbstractResponse<io.etcd.jetcd.api.CampaignResponse> {
|
||||
private final LeaderKey leaderKey;
|
||||
|
||||
public CampaignResponse(io.etcd.jetcd.api.CampaignResponse response) {
|
||||
super(response, response.getHeader());
|
||||
this.leaderKey = toLeaderKey(getResponse().getLeader());
|
||||
}
|
||||
|
||||
this.leaderKey = new LeaderKey(
|
||||
ByteSequence.from(getResponse().getLeader().getName()),
|
||||
ByteSequence.from(getResponse().getLeader().getKey()),
|
||||
getResponse().getLeader().getRev(),
|
||||
getResponse().getLeader().getLease());
|
||||
private static LeaderKey toLeaderKey(io.etcd.jetcd.api.LeaderKey leader) {
|
||||
return new LeaderKey(leader.getName(), leader.getKey(), leader.getRev(), leader.getLease());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resources used for holding leadership of the election.
|
||||
*
|
||||
* @return the leader.
|
||||
*/
|
||||
public LeaderKey getLeader() {
|
||||
return leaderKey;
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
|
||||
package io.etcd.jetcd.election;
|
||||
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
public class LeaderKey {
|
||||
private final ByteSequence name;
|
||||
private final ByteSequence key;
|
||||
private final ByteString name;
|
||||
private final ByteString key;
|
||||
private final long revision;
|
||||
private final long lease;
|
||||
|
||||
public LeaderKey(ByteSequence name, ByteSequence key, long revision, long lease) {
|
||||
public LeaderKey(ByteString name, ByteString key, long revision, long lease) {
|
||||
this.name = name;
|
||||
this.key = key;
|
||||
this.revision = revision;
|
||||
|
@ -32,21 +32,17 @@ public class LeaderKey {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the election identifier that corresponds to the leadership key.
|
||||
*
|
||||
* @return the name.
|
||||
* Returns the election identifier that corresponds to the leadership key. *
|
||||
*/
|
||||
public ByteSequence getName() {
|
||||
public ByteString getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the opaque key representing the ownership of the election. If the key
|
||||
* is deleted, then leadership is lost.
|
||||
*
|
||||
* @return the key.
|
||||
*/
|
||||
public ByteSequence getKey() {
|
||||
public ByteString getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
|
@ -54,8 +50,6 @@ public class LeaderKey {
|
|||
* Returns the creation revision of the key. It can be used to test for ownership
|
||||
* of an election during transactions by testing the key's creation revision
|
||||
* matches rev.
|
||||
*
|
||||
* @return the revision.
|
||||
*/
|
||||
public long getRevision() {
|
||||
return revision;
|
||||
|
@ -63,8 +57,6 @@ public class LeaderKey {
|
|||
|
||||
/**
|
||||
* Returns the lease ID of the election leader.
|
||||
*
|
||||
* @return the lese id.
|
||||
*/
|
||||
public long getLease() {
|
||||
return lease;
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
|
||||
package io.etcd.jetcd.election;
|
||||
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.KeyValue;
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
|
||||
public class LeaderResponse extends AbstractResponse<io.etcd.jetcd.api.LeaderResponse> {
|
||||
private final KeyValue kv;
|
||||
|
@ -30,8 +30,6 @@ public class LeaderResponse extends AbstractResponse<io.etcd.jetcd.api.LeaderRes
|
|||
|
||||
/**
|
||||
* Returns the key-value pair representing the latest leader update.
|
||||
*
|
||||
* @return the kv.
|
||||
*/
|
||||
public KeyValue getKv() {
|
||||
return kv;
|
||||
|
|
|
@ -20,4 +20,5 @@ package io.etcd.jetcd.election;
|
|||
* Signals that leader for given election does not exist.
|
||||
*/
|
||||
public class NoLeaderException extends RuntimeException {
|
||||
public static final NoLeaderException INSTANCE = new NoLeaderException();
|
||||
}
|
||||
|
|
|
@ -20,4 +20,5 @@ package io.etcd.jetcd.election;
|
|||
* Signals that candidate is not a leader at the moment.
|
||||
*/
|
||||
public class NotLeaderException extends RuntimeException {
|
||||
public static final NotLeaderException INSTANCE = new NotLeaderException();
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.etcd.jetcd.election;
|
||||
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
public class ProclaimResponse extends AbstractResponse<io.etcd.jetcd.api.ProclaimResponse> {
|
||||
public ProclaimResponse(io.etcd.jetcd.api.ProclaimResponse response) {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.etcd.jetcd.election;
|
||||
|
||||
import io.etcd.jetcd.impl.AbstractResponse;
|
||||
import io.etcd.jetcd.AbstractResponse;
|
||||
|
||||
public class ResignResponse extends AbstractResponse<io.etcd.jetcd.api.ResignResponse> {
|
||||
public ResignResponse(io.etcd.jetcd.api.ResignResponse response) {
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import io.etcd.jetcd.api.AuthenticateRequest;
|
||||
import io.etcd.jetcd.api.VertxAuthGrpc;
|
||||
import io.grpc.CallCredentials;
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.stub.MetadataUtils;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import static io.etcd.jetcd.Preconditions.checkArgument;
|
||||
|
||||
/**
|
||||
* AuthTokenInterceptor fills header with Auth token of any rpc calls and
|
||||
* refreshes token if the rpc results an invalid Auth token error.
|
||||
*/
|
||||
class AuthCredential extends CallCredentials {
|
||||
public static final Metadata.Key<String> TOKEN = Metadata.Key.of("token", Metadata.ASCII_STRING_MARSHALLER);
|
||||
|
||||
private final ClientConnectionManager manager;
|
||||
private volatile Metadata meta;
|
||||
|
||||
public AuthCredential(ClientConnectionManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyRequestMetadata(RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier) {
|
||||
final Metadata meta = this.meta;
|
||||
|
||||
if (meta != null) {
|
||||
applier.apply(meta);
|
||||
} else {
|
||||
authenticate(applier);
|
||||
}
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
meta = null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void authenticate(MetadataApplier applier) {
|
||||
checkArgument(!manager.builder().user().isEmpty(), "username can not be empty.");
|
||||
checkArgument(!manager.builder().password().isEmpty(), "password can not be empty.");
|
||||
|
||||
VertxAuthGrpc.AuthVertxStub authFutureStub = VertxAuthGrpc.newVertxStub(this.manager.getChannel());
|
||||
|
||||
List<ClientInterceptor> interceptorsChain = new ArrayList<>();
|
||||
if (manager.builder().authHeaders() != null) {
|
||||
Metadata metadata = new Metadata();
|
||||
manager.builder().authHeaders().forEach((BiConsumer<Metadata.Key, Object>) metadata::put);
|
||||
|
||||
interceptorsChain.add(MetadataUtils.newAttachHeadersInterceptor(metadata));
|
||||
}
|
||||
if (manager.builder().authInterceptors() != null) {
|
||||
interceptorsChain.addAll(manager.builder().authInterceptors());
|
||||
}
|
||||
|
||||
if (!interceptorsChain.isEmpty()) {
|
||||
authFutureStub = authFutureStub.withInterceptors(
|
||||
interceptorsChain.toArray(new ClientInterceptor[0]));
|
||||
}
|
||||
|
||||
final ByteString user = ByteString.copyFrom(this.manager.builder().user().getBytes());
|
||||
final ByteString pass = ByteString.copyFrom(this.manager.builder().password().getBytes());
|
||||
|
||||
AuthenticateRequest request = AuthenticateRequest.newBuilder()
|
||||
.setNameBytes(user)
|
||||
.setPasswordBytes(pass)
|
||||
.build();
|
||||
|
||||
try {
|
||||
authFutureStub.authenticate(request)
|
||||
.onFailure(t -> {
|
||||
applier.fail(Status.UNAUTHENTICATED.withCause(t));
|
||||
})
|
||||
.onSuccess(h -> {
|
||||
Metadata meta = new Metadata();
|
||||
meta.put(TOKEN, h.getToken());
|
||||
|
||||
this.meta = meta;
|
||||
|
||||
applier.apply(this.meta);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
applier.fail(Status.UNAUTHENTICATED.withCause(e));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016-2021 The jetcd authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.etcd.jetcd.impl;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.Election;
|
||||
import io.etcd.jetcd.api.CampaignRequest;
|
||||
import io.etcd.jetcd.api.LeaderRequest;
|
||||
import io.etcd.jetcd.api.ProclaimRequest;
|
||||
import io.etcd.jetcd.api.ResignRequest;
|
||||
import io.etcd.jetcd.api.VertxElectionGrpc;
|
||||
import io.etcd.jetcd.election.CampaignResponse;
|
||||
import io.etcd.jetcd.election.LeaderKey;
|
||||
import io.etcd.jetcd.election.LeaderResponse;
|
||||
import io.etcd.jetcd.election.NoLeaderException;
|
||||
import io.etcd.jetcd.election.NotLeaderException;
|
||||
import io.etcd.jetcd.election.ProclaimResponse;
|
||||
import io.etcd.jetcd.election.ResignResponse;
|
||||
import io.etcd.jetcd.support.Errors;
|
||||
import io.etcd.jetcd.support.Util;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.toEtcdException;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
final class ElectionImpl extends Impl implements Election {
|
||||
private final VertxElectionGrpc.ElectionVertxStub stub;
|
||||
private final ByteSequence namespace;
|
||||
|
||||
ElectionImpl(ClientConnectionManager connectionManager) {
|
||||
super(connectionManager);
|
||||
|
||||
this.stub = connectionManager.newStub(VertxElectionGrpc::newVertxStub);
|
||||
this.namespace = connectionManager.getNamespace();
|
||||
}
|
||||
|
||||
// Election operations are done in a context where a client is trying to implement
|
||||
// some fault tolerance related use case; in that type of context, it makes sense to always
|
||||
// apply require leader, since we don't want a client connected to a non-raft-leader server
|
||||
// have an election method just go silent if the server the client happens to be connected to
|
||||
// becomes partitioned from the actual raft-leader server in the etcd servers cluster:
|
||||
// in that scenario and without required leader, an attempt to campaign could block forever
|
||||
// not because some other client is already an election leader, but because the server the client
|
||||
// is connected to is partitioned and can't tell.
|
||||
// With require leader, in that case the call will fail and we give
|
||||
// the client the ability to (a) know (b) retry on a different server.
|
||||
// The retry on a different server should happen automatically if the connection manager is using
|
||||
// a round robin strategy.
|
||||
//
|
||||
// Beware in the context of this election API, the word "leader" is overloaded.
|
||||
// In the paragraph above when we say "raft-leader" we are talking about the etcd server that is a leader
|
||||
// of the etcd servers cluster according to raft, we are not talking about the client that
|
||||
// happens to be the leader of an election using the election API in this file.
|
||||
private VertxElectionGrpc.ElectionVertxStub stubWithLeader() {
|
||||
return Util.applyRequireLeader(true, stub);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CampaignResponse> campaign(ByteSequence electionName, long leaseId, ByteSequence proposal) {
|
||||
requireNonNull(electionName, "election name should not be null");
|
||||
requireNonNull(proposal, "proposal should not be null");
|
||||
|
||||
CampaignRequest request = CampaignRequest.newBuilder()
|
||||
.setName(Util.prefixNamespace(electionName, namespace))
|
||||
.setValue(ByteString.copyFrom(proposal.getBytes()))
|
||||
.setLease(leaseId)
|
||||
.build();
|
||||
|
||||
return wrapConvertException(
|
||||
execute(
|
||||
() -> stubWithLeader().campaign(request),
|
||||
CampaignResponse::new,
|
||||
Errors::isRetryableForNoSafeRedoOp));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ProclaimResponse> proclaim(LeaderKey leaderKey, ByteSequence proposal) {
|
||||
requireNonNull(leaderKey, "leader key should not be null");
|
||||
requireNonNull(proposal, "proposal should not be null");
|
||||
|
||||
ProclaimRequest request = ProclaimRequest.newBuilder()
|
||||
.setLeader(
|
||||
io.etcd.jetcd.api.LeaderKey.newBuilder()
|
||||
.setKey(ByteString.copyFrom(leaderKey.getKey().getBytes()))
|
||||
.setName(ByteString.copyFrom(leaderKey.getName().getBytes()))
|
||||
.setLease(leaderKey.getLease())
|
||||
.setRev(leaderKey.getRevision())
|
||||
.build())
|
||||
.setValue(ByteString.copyFrom(proposal.getBytes()))
|
||||
.build();
|
||||
|
||||
return wrapConvertException(
|
||||
execute(
|
||||
() -> stubWithLeader().proclaim(request),
|
||||
ProclaimResponse::new,
|
||||
Errors::isRetryableForNoSafeRedoOp));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LeaderResponse> leader(ByteSequence electionName) {
|
||||
requireNonNull(electionName, "election name should not be null");
|
||||
|
||||
LeaderRequest request = LeaderRequest.newBuilder()
|
||||
.setName(Util.prefixNamespace(electionName, namespace))
|
||||
.build();
|
||||
|
||||
return wrapConvertException(
|
||||
execute(
|
||||
() -> stubWithLeader().leader(request),
|
||||
response -> new LeaderResponse(response, namespace),
|
||||
Errors::isRetryableForNoSafeRedoOp));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void observe(ByteSequence electionName, Listener listener) {
|
||||
requireNonNull(electionName, "election name should not be null");
|
||||
requireNonNull(listener, "listener should not be null");
|
||||
|
||||
LeaderRequest request = LeaderRequest.newBuilder()
|
||||
.setName(Util.prefixNamespace(electionName, namespace))
|
||||
.build();
|
||||
|
||||
stubWithLeader().observeWithHandler(request,
|
||||
value -> listener.onNext(new LeaderResponse(value, namespace)),
|
||||
ignored -> listener.onCompleted(),
|
||||
error -> listener.onError(toEtcdException(error)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ResignResponse> resign(LeaderKey leaderKey) {
|
||||
requireNonNull(leaderKey, "leader key should not be null");
|
||||
|
||||
ResignRequest request = ResignRequest.newBuilder()
|
||||
.setLeader(
|
||||
io.etcd.jetcd.api.LeaderKey.newBuilder()
|
||||
.setKey(ByteString.copyFrom(leaderKey.getKey().getBytes()))
|
||||
.setName(ByteString.copyFrom(leaderKey.getName().getBytes()))
|
||||
.setLease(leaderKey.getLease())
|
||||
.setRev(leaderKey.getRevision())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
return wrapConvertException(
|
||||
execute(
|
||||
() -> stubWithLeader().resign(request),
|
||||
ResignResponse::new,
|
||||
Errors::isRetryableForNoSafeRedoOp));
|
||||
}
|
||||
|
||||
private <S> CompletableFuture<S> wrapConvertException(CompletableFuture<S> future) {
|
||||
return future.exceptionally(e -> {
|
||||
throw convertException(e);
|
||||
});
|
||||
}
|
||||
|
||||
private RuntimeException convertException(Throwable e) {
|
||||
Throwable cause = e;
|
||||
while (cause != null) {
|
||||
if (cause instanceof StatusRuntimeException) {
|
||||
StatusRuntimeException exception = (StatusRuntimeException) cause;
|
||||
String description = exception.getStatus().getDescription();
|
||||
// different APIs use different messages. we cannot distinguish missing leader error otherwise,
|
||||
// because communicated status is always UNKNOWN
|
||||
if ("election: not leader".equals(description)) {
|
||||
// Candidate is not a leader at the moment.
|
||||
// Note there is a one letter difference, but this exception type is not the same as
|
||||
// NoLeaderException.
|
||||
return new NotLeaderException();
|
||||
}
|
||||
if ("election: no leader".equals(description)) {
|
||||
// Leader for given election does not exist.
|
||||
// Note there is a one letter difference, but this exception type is not the same as
|
||||
// NotLeaderException.
|
||||
return new NoLeaderException();
|
||||
}
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return toEtcdException(e);
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
package io.etcd.jetcd.impl;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.etcd.jetcd.common.exception.EtcdExceptionFactory;
|
||||
import io.etcd.jetcd.support.Errors;
|
||||
import io.grpc.Status;
|
||||
import io.vertx.core.Future;
|
||||
|
||||
import dev.failsafe.Failsafe;
|
||||
import dev.failsafe.RetryPolicy;
|
||||
import dev.failsafe.RetryPolicyBuilder;
|
||||
|
||||
import static io.etcd.jetcd.support.Errors.isAuthStoreExpired;
|
||||
import static io.etcd.jetcd.support.Errors.isInvalidTokenError;
|
||||
|
||||
abstract class Impl {
|
||||
private final Logger logger;
|
||||
private final ClientConnectionManager connectionManager;
|
||||
|
||||
protected Impl(ClientConnectionManager connectionManager) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.logger = LoggerFactory.getLogger(getClass());
|
||||
}
|
||||
|
||||
protected ClientConnectionManager connectionManager() {
|
||||
return this.connectionManager;
|
||||
}
|
||||
|
||||
protected Logger logger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Future of Type S to CompletableFuture of Type T.
|
||||
*
|
||||
* @param sourceFuture the Future to wrap
|
||||
* @param resultConvert the result converter
|
||||
* @return a {@link CompletableFuture} wrapping the given {@link Future}
|
||||
*/
|
||||
protected <S, T> CompletableFuture<T> completable(Future<S> sourceFuture, Function<S, T> resultConvert) {
|
||||
return completable(sourceFuture, resultConvert, EtcdExceptionFactory::toEtcdException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Future of Type S to CompletableFuture of Type T.
|
||||
*
|
||||
* @param sourceFuture the Future to wrap
|
||||
* @param resultConvert the result converter
|
||||
* @param exceptionConverter the exception mapper
|
||||
* @return a {@link CompletableFuture} wrapping the given {@link Future}
|
||||
*/
|
||||
protected <S, T> CompletableFuture<T> completable(
|
||||
Future<S> sourceFuture,
|
||||
Function<S, T> resultConvert,
|
||||
Function<Throwable, Throwable> exceptionConverter) {
|
||||
|
||||
return completable(
|
||||
sourceFuture.compose(
|
||||
r -> Future.succeededFuture(resultConvert.apply(r)),
|
||||
e -> Future.failedFuture(exceptionConverter.apply(e))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Future of Type S to CompletableFuture of Type T.
|
||||
*
|
||||
* @param sourceFuture the Future to wrap
|
||||
* @return a {@link CompletableFuture} wrapping the given {@link Future}
|
||||
*/
|
||||
protected <S> CompletableFuture<S> completable(
|
||||
Future<S> sourceFuture) {
|
||||
return sourceFuture.toCompletionStage().toCompletableFuture();
|
||||
}
|
||||
|
||||
/**
|
||||
* execute the task and retry it in case of failure.
|
||||
*
|
||||
* @param supplier a function that returns a new Future.
|
||||
* @param resultConvert a function that converts Type S to Type T.
|
||||
* @param <S> Source type
|
||||
* @param <T> Converted Type.
|
||||
* @return a CompletableFuture with type T.
|
||||
*/
|
||||
protected <S, T> CompletableFuture<T> execute(
|
||||
Supplier<Future<S>> supplier,
|
||||
Function<S, T> resultConvert,
|
||||
boolean autoRetry) {
|
||||
|
||||
return execute(supplier, resultConvert,
|
||||
autoRetry ? Errors::isRetryableForSafeRedoOp : Errors::isRetryableForNoSafeRedoOp);
|
||||
}
|
||||
|
||||
/**
|
||||
* execute the task and retry it in case of failure.
|
||||
*
|
||||
* @param supplier a function that returns a new Future.
|
||||
* @param resultConvert a function that converts Type S to Type T.
|
||||
* @param doRetry a predicate to determine if a failure has to be retried
|
||||
* @param <S> Source type
|
||||
* @param <T> Converted Type.
|
||||
* @return a CompletableFuture with type T.
|
||||
*/
|
||||
protected <S, T> CompletableFuture<T> execute(
|
||||
Supplier<Future<S>> supplier,
|
||||
Function<S, T> resultConvert,
|
||||
Predicate<Status> doRetry) {
|
||||
|
||||
return Failsafe
|
||||
.with(retryPolicy(doRetry))
|
||||
.with(connectionManager.getExecutorService())
|
||||
.getStageAsync(() -> supplier.get().toCompletionStage())
|
||||
.thenApply(resultConvert);
|
||||
}
|
||||
|
||||
protected <S> RetryPolicy<S> retryPolicy(Predicate<Status> doRetry) {
|
||||
RetryPolicyBuilder<S> policy = RetryPolicy.<S> builder()
|
||||
.onFailure(e -> {
|
||||
logger.warn("retry failure (attempt: {}, error: {})",
|
||||
e.getAttemptCount(),
|
||||
e.getException() != null ? e.getException().getMessage() : "<none>");
|
||||
})
|
||||
.onRetry(e -> {
|
||||
logger.debug("retry (attempt: {}, error: {})",
|
||||
e.getAttemptCount(),
|
||||
e.getLastException() != null ? e.getLastException().getMessage() : "<none>");
|
||||
})
|
||||
.onRetriesExceeded(e -> {
|
||||
logger.warn("maximum number of auto retries reached (attempt: {}, error: {})",
|
||||
e.getAttemptCount(),
|
||||
e.getException() != null ? e.getException().getMessage() : "<none>");
|
||||
})
|
||||
.handleIf(throwable -> {
|
||||
Status status = Status.fromThrowable(throwable);
|
||||
if (isInvalidTokenError(status)) {
|
||||
connectionManager.authCredential().refresh();
|
||||
}
|
||||
if (isAuthStoreExpired(status)) {
|
||||
connectionManager.authCredential().refresh();
|
||||
}
|
||||
return doRetry.test(status);
|
||||
})
|
||||
.withMaxRetries(connectionManager.builder().retryMaxAttempts())
|
||||
.withBackoff(
|
||||
connectionManager.builder().retryDelay(),
|
||||
connectionManager.builder().retryMaxDelay(),
|
||||
connectionManager.builder().retryChronoUnit());
|
||||
|
||||
if (connectionManager.builder().retryMaxDuration() != null) {
|
||||
policy = policy.withMaxDuration(connectionManager.builder().retryMaxDuration());
|
||||
}
|
||||
|
||||
return policy.build();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue