Compare commits

..

No commits in common. "main" and "jetcd-0.5.7" have entirely different histories.

268 changed files with 7876 additions and 10534 deletions

View File

@ -1,25 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
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.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional context**
Add any other context about the problem here.

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,18 +1,20 @@
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: com.google.errorprone:error_prone_core
versions:
- "> 2.3.4, < 3"
- dependency-name: junit:junit
versions:
- ">= 4.13.a, < 4.14"
- dependency-name: org.codehaus.plexus:plexus-compiler-javac-errorprone
versions:
- "> 2.8.6, < 2.9"
- dependency-name: net.revelc.code.formatter:formatter-maven-plugin
versions:
- 2.14.0

View File

@ -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

41
.github/workflows/build-master.yml vendored Normal file
View File

@ -0,0 +1,41 @@
#
# 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.
# 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: master
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set Up Java
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build Project
env:
OSSRH_USERNAME: ${{ secrets. OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets. OSSRH_PASSWORD }}
run: |
./mvnw -V -ntp clean deploy -DskipTests -Pcheck-format --settings etc/mvn-deploy-settings.xml

View File

@ -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.
@ -16,36 +16,24 @@
name: Build PR
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
on:
pull_request:
branches:
- main
workflow_dispatch:
- master
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
with:
fetch-depth: 0
- name: Set Up Java
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build Project
run: |
./mvnw -V -ntp -Pcheck-format clean install

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.
@ -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.
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.

8
.gitignore vendored
View File

@ -1,7 +1,7 @@
.*
!.github
!.gitignore
!gradle/wrapper/gradle-wrapper.jar
!.travis.yml
target/
target-ide/
*.iml
@ -13,6 +13,8 @@ logs/
.project
.env
.sdkmanrc
build/
# Created by './scripts/run_etcd.sh'
/external/
bin/
out/
/jetcd-all/dependency-reduced-pom.xml

107
.mvn/wrapper/MavenWrapperDownloader.java vendored Executable file
View File

@ -0,0 +1,107 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.
*/
import java.util.Properties;
public class MavenWrapperDownloader {
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL =
"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: : " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

BIN
.mvn/wrapper/maven-wrapper.jar vendored Executable file

Binary file not shown.

1
.mvn/wrapper/maven-wrapper.properties vendored Executable file
View File

@ -0,0 +1 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip

View File

@ -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.

5
MAINTAINERS Normal file
View File

@ -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
View File

@ -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>

View File

@ -1,7 +1,8 @@
# jetcd - A Java Client for etcd
[![Build Status](https://github.com/etcd-io/jetcd/actions/workflows/build-main.yml/badge.svg)](https://github.com/etcd-io/jetcd/actions)
[![Build Status](https://github.com/etcd-io/jetcd/workflows/master/badge.svg)](https://github.com/etcd-io/jetcd/actions)
[![License](https://img.shields.io/badge/Licence-Apache%202.0-blue.svg?style=flat-square)](http://www.apache.org/licenses/LICENSE-2.0.html)
[![Maven Central](https://img.shields.io/maven-central/v/io.etcd/jetcd-core.svg?style=flat-square)](https://search.maven.org/#search%7Cga%7C1%7Cio.etcd)
[![GitHub release](https://img.shields.io/github/release/etcd-io/jetcd.svg?style=flat-square)](https://github.com/etcd-io/jetcd/releases)
[![Javadocs](http://www.javadoc.io/badge/io/etcd/jetcd-core.svg)](https://javadoc.io/doc/io.etcd/jetcd-core)
jetcd is the official java client for [etcd](https://github.com/etcd-io/etcd) v3.
@ -10,7 +11,7 @@ jetcd is the official java client for [etcd](https://github.com/etcd-io/etcd) v3
## Java Versions
Java 11 or above is required.
Java 8 or above is required.
## Download
@ -23,31 +24,23 @@ 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"
}
```
### Usage
```java
// create client using endpoints
Client client = Client.builder().endpoints("http://etcd0:2379", "http://etcd1:2379", "http://etcd2:2379").build();
```
```java
// create client using target which enable using any name resolution mechanism provided
// by grpc-java (i.e. dns:///foo.bar.com:2379)
Client client = Client.builder().target("ip:///etcd0:2379,etcd1:2379,etcd2:2379").build();
```
```java
// create client
Client client = Client.builder().endpoints("http://localhost:2379").build();
KV kvClient = client.getKVClient();
ByteSequence key = ByteSequence.from("test_key".getBytes());
ByteSequence value = ByteSequence.from("test_value".getBytes());
@ -63,13 +56,12 @@ 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/).
### Examples
The [jetcd-ctl](https://github.com/etcd-io/jetcd/tree/master/jetcd-ctl) is a standalone projects that show usage of jetcd.
The [examples](https://github.com/etcd-io/jetcd/tree/master/jetcd-examples) are standalone projects that show usage of jetcd.
## Launcher
@ -77,15 +69,12 @@ The `io.etcd:jetcd-test` offers a convenient utility to programmatically start &
```java
import io.etcd.jetcd.Client;
import io.etcd.jetcd.launcher.EtcdCluster;
import io.etcd.jetcd.test.EtcdClusterExtension;
import org.junit.jupiter.api.extension.RegisterExtension;
@RegisterExtension
public static final EtcdClusterExtension cluster = EtcdClusterExtension.builder()
.withNodes(1)
.build();
Client client = Client.builder().endpoints(cluster.clientEndpoints()).build();
@RegisterExtension static final EtcdCluster etcd = new EtcdClusterExtension("test-etcd", 1);
Client client = Client.builder().endpoints(etcd.getClientEndpoints()).build();
```
This launcher uses the Testcontainers framework.
@ -99,10 +88,10 @@ 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
mvn clean install -DskipTests
```
## Running tests
@ -110,8 +99,33 @@ The project can be built with [Gradle](https://gradle.org/):
The project is tested against a three node `etcd` setup started with the Launcher (above) :
```sh
$ ./gradlew test
```
$ mvn test
...
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running TestSuite
[WARNING] Tests run: 104, Failures: 0, Errors: 0, Skipped: 3, Time elapsed: 31.308 s - in TestSuite
[INFO]
[INFO] Results:
[INFO]
[WARNING] Tests run: 104, Failures: 0, Errors: 0, Skipped: 3
...
[INFO] Reactor Summary:
[INFO]
[INFO] jetcd .............................................. SUCCESS [ 0.010 s]
[INFO] jetcd-core ......................................... SUCCESS [ 55.480 s]
[INFO] jetcd-discovery-dns-srv ............................ SUCCESS [ 3.225 s]
[INFO] jetcd-watch-example ................................ SUCCESS [ 0.291 s]
[INFO] jetcd-simple-ctl ................................... SUCCESS [ 0.028 s]
[INFO] jetcd-examples ..................................... SUCCESS [ 0.000 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 59.929 s
[INFO] Finished at: 2018-02-13T12:51:13-08:00
[INFO] Final Memory: 84M/443M
````
### Troubleshooting
@ -119,14 +133,17 @@ 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)
* IRC: #[etcd](irc://irc.freenode.org:6667/#etcd) on freenode.org
## Contributing
See [CONTRIBUTING](https://github.com/etcd-io/jetcd/blob/master/CONTRIBUTING.md) for details on submitting patches and the contribution workflow.
## Reporting bugs
See [reporting bugs](https://github.com/etcd-io/etcd/blob/master/Documentation/reporting-bugs.md) for details about reporting any issues.
## License
jetcd is under the Apache 2.0 license. See the [LICENSE](https://github.com/etcd-io/jetcd/blob/master/LICENSE) file for details.

View File

@ -1,100 +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.
*/
buildscript {
repositories {
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()}"
}
}
apply from: "${rootProject.projectDir}/gradle/versions.gradle"
apply from: "${rootProject.projectDir}/gradle/publish.gradle"
apply from: "${rootProject.projectDir}/gradle/release.gradle"
group = 'io.etcd'
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
subprojects {
apply from: "${rootProject.projectDir}/gradle/style.gradle"
apply from: "${rootProject.projectDir}/gradle/quality.gradle"
apply from: "${rootProject.projectDir}/gradle/publishing-release.gradle"
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'
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
}
test {
useJUnitPlatform()
maxParallelForks = Runtime.runtime.availableProcessors() ?: 1
retry {
maxRetries = 1
maxFailures = 5
}
}
testing {
suites {
test {
useJUnitJupiter()
}
}
}
testlogger {
theme 'mocha-parallel'
showStandardStreams false
}
tasks.register('allDeps', DependencyReportTask)
tasks.withType(AbstractArchiveTask).configureEach {
preserveFileTimestamps = false
reproducibleFileOrder = true
}
}

View File

@ -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 employers
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>.

View File

@ -26,3 +26,6 @@ for (KeyValue kv : response.getKvs()) {
);
}
----

View File

@ -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`.

View File

@ -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
----

View File

@ -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);
});
```

View File

@ -12,15 +12,13 @@ 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.
# 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) and process cancel with [cancelWatch function](#cancelwatch-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.
@ -46,13 +44,6 @@ Cancel the watch task with the watcher, the `onCanceled` will be called after su
1. The watcher will be removed from [watchers](#watchers-instance) map.
2. If the [watchers](#watchers-instance) map contain the watcher, it will be moved to [cancelWatchers](#cancelwatchers) and send cancel request to [requestStream](#requeststream-instance).
## requestProgress function
Send the latest revision processed to all active [watchers](#watchers-instance)
1. Send a progress request to [requestStream](#requeststream-instance).
2. Working watchers will receive a WatchResponse containing the latest revision number. All future revision numbers are guaranteed to be greater than or equal to the received revision number.
## requestStream instance
StreamObserver instance
@ -116,4 +107,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.

View File

@ -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>

View File

@ -1,7 +0,0 @@
0=java
1=javax
2=org
2=io
4=com
5=
6=\#

View File

@ -1,4 +1,4 @@
Copyright $YEAR The jetcd authors
Copyright ${license.git.copyrightYears} 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.
@ -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.

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>ossrh</id>
<username>${env.OSSRH_USERNAME}</username>
<password>${env.OSSRH_PASSWORD}</password>
</server>
</servers>
</settings>

View File

@ -1,93 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<ruleset name="PMD-Rules"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<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"/>
<rule ref="category/java/bestpractices.xml/UnusedPrivateField"/>
<rule ref="category/java/bestpractices.xml/UnusedPrivateMethod"/>
<rule ref="category/java/bestpractices.xml/OneDeclarationPerLine"/>
<rule ref="category/java/bestpractices.xml/UseCollectionIsEmpty"/>
<rule ref="category/java/bestpractices.xml/PreserveStackTrace"/>
<rule ref="category/java/bestpractices.xml/SimplifiableTestAssertion" />
<rule ref="category/java/codestyle.xml/ExtendsObject"/>
<rule ref="category/java/codestyle.xml/ForLoopShouldBeWhileLoop"/>
<rule ref="category/java/codestyle.xml/UnnecessaryFullyQualifiedName"/>
<rule ref="category/java/codestyle.xml/UnnecessaryModifier"/>
<rule ref="category/java/codestyle.xml/UnnecessaryReturn"/>
<rule ref="category/java/codestyle.xml/UselessParentheses"/>
<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>
</rule>
<rule ref="category/java/design.xml/SimplifiedTernary">
<priority>4</priority>
</rule>
<rule ref="category/java/design.xml/CollapsibleIfStatements">
<priority>4</priority>
</rule>
<rule ref="category/java/design.xml/UselessOverridingMethod">
<priority>4</priority>
</rule>
<rule ref="category/java/errorprone.xml/AvoidBranchingStatementAsLastInLoop"/>
<rule ref="category/java/errorprone.xml/AvoidMultipleUnaryOperators"/>
<rule ref="category/java/errorprone.xml/AvoidUsingOctalValues"/>
<rule ref="category/java/errorprone.xml/BrokenNullCheck"/>
<rule ref="category/java/errorprone.xml/ClassCastExceptionWithToArray"/>
<rule ref="category/java/errorprone.xml/DontUseFloatTypeForLoopIndices"/>
<rule ref="category/java/errorprone.xml/EmptyCatchBlock">
<priority>4</priority>
<properties>
<property name="allowCommentedBlocks" value="true"/>
<property name="allowExceptionNameRegex" value="ignored"/>
</properties>
</rule>
<rule ref="category/java/errorprone.xml/JumbledIncrementer"/>
<rule ref="category/java/errorprone.xml/MisplacedNullCheck"/>
<rule ref="category/java/errorprone.xml/OverrideBothEqualsAndHashcode"/>
<rule ref="category/java/errorprone.xml/ReturnFromFinallyBlock"/>
<rule ref="category/java/errorprone.xml/UnconditionalIfStatement"/>
<rule ref="category/java/errorprone.xml/UnnecessaryConversionTemporary"/>
<rule ref="category/java/errorprone.xml/UnusedNullCheckInEquals"/>
<rule ref="category/java/errorprone.xml/UselessOperationOnImmutable"/>
<rule ref="category/java/errorprone.xml/ConstructorCallsOverridableMethod"/>
<rule ref="category/java/errorprone.xml/DoNotTerminateVM"/>
<rule ref="category/java/multithreading.xml/AvoidThreadGroup"/>
<rule ref="category/java/multithreading.xml/DontCallThreadRun"/>
<rule ref="category/java/performance.xml/BigIntegerInstantiation"/>
<rule ref="category/java/bestpractices.xml/PrimitiveWrapperInstantiation" />
</ruleset>

35
etc/versions-rules.xml Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<ruleset xmlns="http://mojo.codehaus.org/versions-maven-plugin/rule/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
comparisonMethod="maven"
xsi:schemaLocation="http://mojo.codehaus.org/versions-maven-plugin/rule/2.0.0 http://mojo.codehaus.org/versions-maven-plugin/xsd/rule-2.0.0.xsd">
<ignoreVersions>
<!-- Ignore Alpha's, Beta's, release candidates and milestones -->
<ignoreVersion type="regex">(?i).*Alpha(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).*a(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).*Beta(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).*-B(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).*RC(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).*CR(?:-?\d+)?</ignoreVersion>
<ignoreVersion type="regex">(?i).*M(?:-?\d+)?</ignoreVersion>
</ignoreVersions>
<rules>
</rules>
</ruleset>

View File

@ -1,10 +0,0 @@
#
# project
#
gitProject = https://github.com/etcd-io/jetcd
gitURL = git@github.com/etcd-io/jetcd.git
#
# gradle
#
org.gradle.parallel = true

View File

@ -1,86 +0,0 @@
[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"
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"
[libraries]
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
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" }
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"}
commonsIo = { module = "commons-io:commons-io", version.ref = "commonsIo" }
commonsCompress = { module = "org.apache.commons:commons-compress", version.ref = "commonCompress" }
mockitoCore = { module = "org.mockito:mockito-core", version.ref = "mockito" }
mockitoJunit = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }
grpcCore = { module = "io.grpc:grpc-core", version.ref = "grpc" }
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" }
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+" }
[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" ]
[plugins]
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadowPlugin" }
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }

View File

@ -1,39 +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.
*/
apply plugin: "signing"
apply plugin: "maven-publish"
apply plugin: "io.github.gradle-nexus.publish-plugin"
ext {
if (!project.hasProperty('nexusUsername')) {
nexusUsername = "$System.env.NEXUS_USERNAME"
}
if (!project.hasProperty('nexusPassword')) {
nexusPassword = "$System.env.NEXUS_PASSWORD"
}
}
nexusPublishing {
repositories {
sonatype {
username = nexusUsername
password = nexusPassword
}
}
}
//afterReleaseBuild.dependsOn publishToSonatype

View File

@ -1,25 +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.
*/
apply plugin: 'maven-publish'
task pubblications {
doLast {
project.publishing.publications.each { publication ->
println "Publication $publication.name [$publication.groupId/$publication.artifactId/$publication.version]"
}
}
}

View File

@ -1,100 +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.
*/
apply plugin: 'java-library'
apply plugin: 'maven-publish'
apply plugin: 'signing'
java {
withJavadocJar()
withSourcesJar()
}
publishing {
publications {
"${project.name}"(MavenPublication) {
groupId = rootProject.group
from components.java
pom {
name = project.name
description = project.name
url = "${gitProject}"
scm {
url = "${gitProject}"
connection = "scm:${gitProject}"
developerConnection = "scm:${gitURL}"
}
licenses {
license {
name = 'The Apache Software License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution = 'repo'
}
}
developers {
developer {
id = 'lburgazzoli'
name = 'Luca Burgazzoli'
organization = 'Red Hat'
organizationUrl = 'http://redhat.com'
}
developer {
name = 'Fanmin Shi'
organization = 'CoreOS'
organizationUrl = 'http://coreos.com'
}
developer {
name = 'Xiang Li'
organization = 'CoreOS'
organizationUrl = 'http://coreos.com'
}
developer {
name = 'Anthony Romano'
organization = 'CoreOS'
organizationUrl = 'http://coreos.com'
}
}
}
}
}
}
signing {
required {
!version.endsWith('SNAPSHOT')
}
if (!version.endsWith('SNAPSHOT')) {
useGpgCmd()
}
sign publishing.publications."${project.name}"
}
check.dependsOn javadoc
javadoc {
if(JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html5', true)
}
exclude "**/io/etcd/jetcd/api/**"
}

View File

@ -1,37 +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.
*/
apply plugin: "net.ltgt.errorprone"
apply plugin: "pmd"
dependencies {
errorprone(libs.errorprone)
errorprone(libs.errorproneAnnotations)
errorproneJavac(libs.errorproneJavac)
}
tasks.withType(JavaCompile).configureEach {
options.errorprone {
excludedPaths = '.*/generated/.*'
disableWarningsInGeneratedCode = true
}
options.deprecation = true
}
pmd {
consoleOutput = true
ruleSets = [ "${project.rootDir}/etc/pmd-ruleset.xml" ]
}

View File

@ -1,28 +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.
*/
apply plugin: "pl.allegro.tech.build.axion-release"
scmVersion {
tag {
prefix = 'jetcd-'
}
}
allprojects {
project.version = scmVersion.version
}

View File

@ -1,29 +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.
*/
apply plugin: "com.diffplug.spotless"
spotless {
java {
removeUnusedImports()
trimTrailingWhitespace()
indentWithSpaces(4)
endWithNewline()
importOrderFile(rootProject.file('etc/eclipse.importorder'))
eclipse().configFile(rootProject.file('etc/eclipse-formatter-config.xml'))
targetExclude("build/generated/**/*.java")
}
}

View File

@ -1,32 +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.
*/
apply plugin: 'com.github.ben-manes.versions'
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)
}
}
task deps {
dependsOn dependencyUpdates
}

Binary file not shown.

View File

@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
gradlew vendored
View File

@ -1,251 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original 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
#
# https://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.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# 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
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
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
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
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.
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
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
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.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# 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.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
gradlew.bat vendored
View File

@ -1,94 +0,0 @@
@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

135
jetcd-all/pom.xml Normal file
View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.etcd</groupId>
<artifactId>jetcd-parent</artifactId>
<version>0.5.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetcd-all</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>io.etcd</groupId>
<artifactId>jetcd-common</artifactId>
<exclusions>
<exclusion>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-compat-qual</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.j2objc</groupId>
<artifactId>j2objc-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.etcd</groupId>
<artifactId>jetcd-core</artifactId>
<exclusions>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-compat-qual</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.etcd</groupId>
<artifactId>jetcd-extensions</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createSourcesJar>true</createSourcesJar>
<createDependencyReducedPom>true</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
<minimizeJar>false</minimizeJar>
<artifactSet>
<excludes>
<exclude>org.slf4j:slf4j-api</exclude>
<exclude>com.google.errorprone:error_prone_annotations</exclude>
</excludes>
</artifactSet>
<relocations>
<relocation>
<pattern>com.google</pattern>
<shadedPattern>io.etcd.jetcd.shaded.com.google</shadedPattern>
</relocation>
<relocation>
<pattern>io.grpc</pattern>
<shadedPattern>io.etcd.jetcd.shaded.io.grpc</shadedPattern>
</relocation>
<relocation>
<pattern>io.netty</pattern>
<shadedPattern>io.etcd.jetcd.shaded.io.netty</shadedPattern>
</relocation>
<relocation>
<pattern>javax.annotation</pattern>
<shadedPattern>io.etcd.jetcd.shaded.javax.annotation</shadedPattern></relocation>
<relocation>
<pattern>io.opencensus</pattern>
<shadedPattern>io.etcd.jetcd.shaded.io.opencensus</shadedPattern>
</relocation>
<relocation>
<pattern>io.opencensus</pattern>
<shadedPattern>io.etcd.jetcd.shaded.io.opencensus</shadedPattern>
</relocation>
<relocation>
<pattern>net.jodah</pattern>
<shadedPattern>io.etcd.jetcd.shaded.net.jodah</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -14,12 +14,15 @@
* limitations under the License.
*/
package io.etcd.jetcd.all;
dependencies {
api libs.slf4j
api libs.guava
api libs.grpcCore
testImplementation libs.bundles.testing
testRuntimeOnly libs.bundles.log4j
/**
* Placeholder.
*/
public final class JetcdAll {
/**
* Private c-tor.
*/
private JetcdAll() {
}
}

View File

@ -14,7 +14,4 @@
* limitations under the License.
*/
dependencies {
api libs.slf4j
}
package io.etcd.jetcd.all;

69
jetcd-common/pom.xml Normal file
View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.etcd</groupId>
<artifactId>jetcd-parent</artifactId>
<version>0.5.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetcd-common</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-core</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>io/etcd/jetcd/api/**</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -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();
}

View File

@ -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;

View File

@ -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,13 +27,11 @@ 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);
}
/**
* Returns the error code associated with this exception.
*
* @return a {@link ErrorCode}
*/
public ErrorCode getErrorCode() {
return code;

View File

@ -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);
}

View File

@ -1,40 +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.
*/
dependencies {
api project(':jetcd-grpc')
api project(':jetcd-api')
api project(':jetcd-common')
api libs.slf4j
api libs.guava
api libs.failsafe
//compileOnly libs.javaxAnnotation
compileOnly 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
testRuntimeOnly libs.bundles.log4j
}

232
jetcd-core/pom.xml Normal file
View File

@ -0,0 +1,232 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.etcd</groupId>
<artifactId>jetcd-parent</artifactId>
<version>0.5.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetcd-core</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jetcd-common</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-core</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-grpclb</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>net.jodah</groupId>
<artifactId>failsafe</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-migrationsupport</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-testing</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>${netty-tcnative.version}</version>
<classifier>${os.detected.classifier}</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jetcd-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${os-maven-plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>detect</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>test</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${basedir}/target/generated-sources/protobuf/grpc-java</source>
<source>${basedir}/target/generated-sources/protobuf/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>io/etcd/jetcd/api/**</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -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 {

View File

@ -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,7 @@ 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.api.Permission.Type;
import io.etcd.jetcd.auth.AuthDisableResponse;
import io.etcd.jetcd.auth.AuthEnableResponse;
import io.etcd.jetcd.auth.AuthRoleAddResponse;
@ -53,235 +52,180 @@ 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) {
case WRITE:
type = io.etcd.jetcd.api.Permission.Type.WRITE;
type = Type.WRITE;
break;
case READWRITE:
type = io.etcd.jetcd.api.Permission.Type.READWRITE;
type = Type.READWRITE;
break;
case READ:
type = io.etcd.jetcd.api.Permission.Type.READ;
type = Type.READ;
break;
default:
type = io.etcd.jetcd.api.Permission.Type.UNRECOGNIZED;
type = Type.UNRECOGNIZED;
break;
}
io.etcd.jetcd.api.Permission perm = io.etcd.jetcd.api.Permission.newBuilder()
.setKey(ByteString.copyFrom(key.getBytes()))
.setRangeEnd(ByteString.copyFrom(rangeEnd.getBytes()))
.setPermType(type)
.build();
io.etcd.jetcd.api.Permission perm = io.etcd.jetcd.api.Permission.newBuilder().setKey(key.getByteString())
.setRangeEnd(rangeEnd.getByteString()).setPermType(type).build();
AuthRoleGrantPermissionRequest roleGrantPermissionRequest = AuthRoleGrantPermissionRequest.newBuilder()
.setNameBytes(ByteString.copyFrom(role.getBytes()))
.setPerm(perm)
.build();
return completable(
this.stub.roleGrantPermission(roleGrantPermissionRequest),
AuthRoleGrantPermissionResponse::new);
.setNameBytes(role.getByteString()).setPerm(perm).build();
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());
}
}

View File

@ -17,7 +17,6 @@
package io.etcd.jetcd;
import java.nio.charset.Charset;
import java.util.Objects;
import com.google.protobuf.ByteString;
@ -32,7 +31,6 @@ public final class ByteSequence {
private final ByteString byteString;
private ByteSequence(ByteString byteString) {
Objects.requireNonNull(byteString, "byteString should not be null");
this.byteString = byteString;
this.hashVal = byteString.hashCode();
}
@ -45,10 +43,9 @@ public final class ByteSequence {
* byte sequence represented by this string; <code>false</code> otherwise.
*/
public boolean startsWith(ByteSequence prefix) {
if (prefix == null) {
return false;
}
return byteString.startsWith(prefix.byteString);
ByteString baseByteString = this.getByteString();
ByteString prefixByteString = prefix.getByteString();
return baseByteString.startsWith(prefixByteString);
}
/**
@ -58,19 +55,7 @@ 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));
return new ByteSequence(this.byteString.concat(other.getByteString()));
}
/**
@ -122,6 +107,10 @@ public final class ByteSequence {
return hashVal;
}
ByteString getByteString() {
return this.byteString;
}
public String toString(Charset charset) {
return byteString.toString(charset);
}
@ -138,11 +127,6 @@ public final class ByteSequence {
return byteString.size();
}
@Override
public String toString() {
return byteString.toStringUtf8();
}
/**
* Create new ByteSequence from a String.
*
@ -152,26 +136,16 @@ public final class ByteSequence {
*/
public static ByteSequence from(String source, Charset charset) {
byte[] bytes = source.getBytes(charset);
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));
}
}

View File

@ -26,58 +26,42 @@ package io.etcd.jetcd;
public interface Client extends AutoCloseable {
/**
* Returns the {@link Auth} client.
*
* @return the client.
* @return the {@link Auth} client.
*/
Auth getAuthClient();
/**
* Returns the {@link KV} client.
*
* @return the client.
* @return the {@link KV} client.
*/
KV getKVClient();
/**
* Returns the {@link Cluster} client.
*
* @return the client.
* @return the {@link Cluster} client.
*/
Cluster getClusterClient();
/**
* Returns the {@link Maintenance} client.
*
* @return the client.
* @return the {@link Maintenance} client.
*/
Maintenance getMaintenanceClient();
/**
* Returns the {@link Lease} client.
*
* @return the client.
* @return the {@link Lease} client.
*/
Lease getLeaseClient();
/**
* Returns the {@link Watch} client.
*
* @return the client.
* @return the {@link Watch} client.
*/
Watch getWatchClient();
/**
* Returns the {@link Lock} client.
*
* @return the client.
* @return the {@link Lock} client.
*/
Lock getLockClient();
/**
* Returns the {@link Election} client.
*
* @return the client.
* @return the {@link Election} client.
*/
Election getElectionClient();
@ -85,9 +69,7 @@ public interface Client extends AutoCloseable {
void close();
/**
* Returns a new {@link ClientBuilder}.
*
* @return the builder.
* @return a new {@link ClientBuilder}.
*/
static ClientBuilder builder() {
return new ClientBuilder();

View File

@ -19,34 +19,38 @@ 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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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 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 {
private String target;
private final Set<URI> endpoints = new HashSet<>();
private ByteSequence user;
private ByteSequence password;
private ExecutorService executorService;
@ -55,64 +59,52 @@ public final class ClientBuilder implements Cloneable {
private String authority;
private Integer maxInboundMessageSize;
private Map<Metadata.Key<?>, Object> headers;
private Map<Metadata.Key<?>, Object> authHeaders;
private List<ClientInterceptor> interceptors;
private List<ClientInterceptor> authInterceptors;
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);
private Boolean keepaliveWithoutCalls = true;
private Duration retryMaxDuration;
private Duration connectTimeout;
private boolean waitForReady = true;
private Vertx vertx;
private boolean discovery;
ClientBuilder() {
}
/**
* Gets the etcd target.
* gets the endpoints for the builder.
*
* @return the etcd target.
* @return the list of endpoints configured for the builder
*/
public String target() {
return target;
public Collection<URI> endpoints() {
return Collections.unmodifiableCollection(this.endpoints);
}
/**
* configure etcd server endpoints.
*
* @param target etcd server target
* @return this builder to train
* @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");
this.target = target;
return this;
}
/**
* configure etcd server endpoints using the {@link IPNameResolver}.
*
* @param endpoints etcd server endpoints, at least one
* @return this builder to train
* @throws NullPointerException if endpoints is null or one of endpoint is null
* @throws IllegalArgumentException if some endpoint is invalid
*/
public ClientBuilder endpoints(String... endpoints) {
return endpoints(
Stream.of(endpoints).map(URI::create).toArray(URI[]::new));
public ClientBuilder endpoints(Collection<URI> endpoints) {
checkNotNull(endpoints, "endpoints can't be null");
for (URI endpoint : endpoints) {
checkNotNull(endpoint, "endpoint can't be null");
checkArgument(endpoint.toString().trim().length() > 0, "invalid endpoint: endpoint=" + endpoint);
this.endpoints.add(endpoint);
}
return this;
}
/**
* configure etcd server endpoints using the {@link IPNameResolver}.
* configure etcd server endpoints.
*
* @param endpoints etcd server endpoints, at least one
* @return this builder to train
@ -120,47 +112,25 @@ public final class ClientBuilder implements Cloneable {
* @throws IllegalArgumentException if some endpoint is invalid
*/
public ClientBuilder endpoints(URI... endpoints) {
checkNotNull(endpoints, "endpoints can't be null");
return endpoints(Arrays.asList(endpoints));
}
/**
* configure etcd server endpoints using the {@link IPNameResolver}.
* configure etcd server endpoints.
*
* @param endpoints etcd server endpoints, at least one
* @return this builder to train
* @throws NullPointerException if endpoints is null or one of endpoint is null
* @throws IllegalArgumentException if some endpoint is invalid
*/
public ClientBuilder endpoints(Iterable<URI> endpoints) {
Objects.requireNonNull(endpoints, "endpoints can't be null");
endpoints.forEach(e -> {
if (e.getHost() == null) {
throw new IllegalArgumentException("Unable to compute target from endpoint: '" + e + "'");
}
});
final String target = StreamSupport.stream(endpoints.spliterator(), false)
.map(e -> e.getHost() + (e.getPort() != -1 ? ":" + e.getPort() : ""))
.distinct()
.collect(Collectors.joining(","));
if (Strings.isNullOrEmpty(target)) {
throw new IllegalArgumentException("Unable to compute target from endpoints: '" + endpoints + "'");
}
return target(
String.format(
"%s://%s/%s",
IPNameResolver.SCHEME,
authority != null ? authority : "",
target));
public ClientBuilder endpoints(String... endpoints) {
return endpoints(Util.toURIs(Arrays.asList(endpoints)));
}
/**
* Returns the auth user
*
* @return the user.
* @return the auth user
*/
public ByteSequence user() {
return user;
@ -174,15 +144,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.
* @return the auth password
*/
public ByteSequence password() {
return password;
@ -196,15 +164,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.
* @return the namespace of each key used
*/
public ByteSequence namespace() {
return namespace;
@ -219,15 +185,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.
* @return the executor service
*/
public ExecutorService executorService() {
return executorService;
@ -241,7 +205,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 +218,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;
}
@ -269,9 +233,7 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Returns the ssl context
*
* @return the ssl context.
* @return the ssl context
*/
public SslContext sslContext() {
return sslContext;
@ -304,17 +266,13 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Returns The authority used to authenticate connections to servers.
*
* @return the authority.
* @return The authority used to authenticate connections to servers.
*/
public String authority() {
return authority;
}
/**
* Sets the authority used to authenticate connections to servers.
*
* @param authority the authority used to authenticate connections to servers.
* @return this builder
*/
@ -324,18 +282,14 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Returns the maximum message size allowed for a single gRPC frame.
*
* @return max inbound message size.
* @return the maximum message size allowed for a single gRPC frame.
*/
public Integer maxInboundMessageSize() {
return maxInboundMessageSize;
}
/**
* Sets the maximum message size allowed for a single gRPC frame.
*
* @param maxInboundMessageSize the maximum message size allowed for a single gRPC frame.
* @param maxInboundMessageSize Sets the maximum message size allowed for a single gRPC frame.
* @return this builder
*/
public ClientBuilder maxInboundMessageSize(Integer maxInboundMessageSize) {
@ -344,18 +298,14 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Returns the headers to be added to http request headers
*
* @return headers.
* @return the headers to be added to http request headers
*/
public Map<Metadata.Key<?>, Object> headers() {
return headers == null ? Collections.emptyMap() : Collections.unmodifiableMap(headers);
}
/**
* Sets headers to be added to http request headers.
*
* @param headers headers to be added to http request headers.
* @param headers Sets headers to be added to http request headers.
* @return this builder
*/
public ClientBuilder headers(Map<Metadata.Key<?>, Object> headers) {
@ -365,8 +315,6 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Set headers.
*
* @param key Sets an header key to be added to http request headers.
* @param value Sets an header value to be added to http request headers.
* @return this builder
@ -382,56 +330,14 @@ 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);
}
/**
* Set the auth headers.
*
* @param authHeaders Sets headers to be added to auth request headers.
* @return this builder
*/
public ClientBuilder authHeaders(Map<Metadata.Key<?>, Object> authHeaders) {
this.authHeaders = new HashMap<>(authHeaders);
return this;
}
/**
* Add an auth header.
*
* @param key Sets an header key to be added to auth request headers.
* @param value Sets an header value to be added to auth request headers.
* @return this builder
*/
public ClientBuilder authHeader(String key, String value) {
if (this.authHeaders == null) {
this.authHeaders = new HashMap<>();
}
this.authHeaders.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value);
return this;
}
/**
* Returns the interceptors
*
* @return the interceptors.
* @return the interceptors
*/
public List<ClientInterceptor> interceptors() {
return interceptors;
}
/**
* Set the interceptors.
*
* @param interceptors the interceptors.
* @param interceptors Set the interceptors.
* @return this builder
*/
public ClientBuilder interceptors(List<ClientInterceptor> interceptors) {
@ -441,8 +347,6 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Add an interceptor.
*
* @param interceptor an interceptors to add
* @param interceptors additional interceptors
* @return this builder
@ -459,56 +363,13 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Returns the auth interceptors
*
* @return the interceptors.
*/
public List<ClientInterceptor> authInterceptors() {
return authInterceptors;
}
/**
* Set the auth interceptors.
*
* @param interceptors Set the interceptors to add to the auth chain
* @return this builder
*/
public ClientBuilder authInterceptors(List<ClientInterceptor> interceptors) {
this.authInterceptors = new ArrayList<>(interceptors);
return this;
}
/**
* Add an auth interceptor.
*
* @param interceptor an interceptors to add to the auth chain
* @param interceptors additional interceptors to add to the auth chain
* @return this builder
*/
public ClientBuilder authInterceptors(ClientInterceptor interceptor, ClientInterceptor... interceptors) {
if (this.authInterceptors == null) {
this.authInterceptors = new ArrayList<>();
}
this.authInterceptors.add(interceptor);
this.authInterceptors.addAll(Arrays.asList(interceptors));
return this;
}
/**
* Returns The delay between retries.
*
* @return the retry delay.
* @return The delay between retries.
*/
public long retryDelay() {
return retryDelay;
}
/**
* The delay between retries.
*
* @param retryDelay The delay between retries.
* @return this builder
*/
@ -518,17 +379,13 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Returns the max backing off delay between retries
*
* @return max retry delay.
* @return the max backing off delay between retries
*/
public long retryMaxDelay() {
return retryMaxDelay;
}
/**
* Set the max backing off delay between retries.
*
* @param retryMaxDelay The max backing off delay between retries.
* @return this builder
*/
@ -537,31 +394,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;
}
@ -570,8 +402,7 @@ public final class ClientBuilder implements Cloneable {
* The interval for gRPC keepalives.
* The current minimum allowed by gRPC is 10s
*
* @param keepaliveTime time between keepalives
* @return this builder
* @param keepaliveTime time between keepalives
*/
public ClientBuilder keepaliveTime(Duration keepaliveTime) {
// gRPC uses a minimum keepalive time of 10s, if smaller values are given.
@ -580,11 +411,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;
}
@ -592,8 +418,7 @@ public final class ClientBuilder implements Cloneable {
/**
* The timeout for gRPC keepalives
*
* @param keepaliveTimeout the gRPC keep alive timeout.
* @return this builder
* @param keepaliveTimeout the gRPC keep alive timeout.
*/
public ClientBuilder keepaliveTimeout(Duration keepaliveTimeout) {
this.keepaliveTimeout = keepaliveTimeout;
@ -607,8 +432,7 @@ public final class ClientBuilder implements Cloneable {
/**
* Keepalive option for gRPC
*
* @param keepaliveWithoutCalls the gRPC keep alive without calls.
* @return this builder
* @param keepaliveWithoutCalls the gRPC keep alive without calls.
*/
public ClientBuilder keepaliveWithoutCalls(Boolean keepaliveWithoutCalls) {
this.keepaliveWithoutCalls = keepaliveWithoutCalls;
@ -616,17 +440,13 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Returns he retries period unit.
*
* @return the chrono unit.
* @return he retries period unit.
*/
public ChronoUnit retryChronoUnit() {
return retryChronoUnit;
}
/**
* Sets the retries period unit.
*
* @param retryChronoUnit the retries period unit.
* @return this builder
*/
@ -636,26 +456,20 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Returns the retries max duration.
*
* @return retry max duration.
* @return the retries max duration.
*/
public Duration retryMaxDuration() {
return retryMaxDuration;
}
/**
* Returns the connect timeout.
*
* @return connect timeout.
* @return the connect timeout.
*/
public Duration connectTimeout() {
return connectTimeout;
}
/**
* Set the retries max duration.
*
* @param retryMaxDuration the retries max duration.
* @return this builder
*/
@ -665,10 +479,8 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Set the connection timeout.
*
* @param connectTimeout Sets the connection timeout.
* Clients connecting to fault tolerant etcd clusters (eg, clusters with more than 2 etcd server
* Clients connecting to fault tolerant etcd clusters (eg, clusters with >= 3 etcd server
* peers/endpoints)
* should consider a value that will allow switching timely from a crashed/partitioned peer to
* a consensus peer.
@ -687,49 +499,18 @@ public final class ClientBuilder implements Cloneable {
}
/**
* Enable gRPC's wait for ready semantics.
*
* @return if this client uses gRPC's wait for ready semantics.
* @see <a href="https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md">gRPC Wait for Ready Semantics</a>
* @return if the endpoint represent a discovery address using dns+srv.
*/
public boolean waitForReady() {
return waitForReady;
public boolean discovery() {
return discovery;
}
/**
* Configure the gRPC's wait for ready semantics.
*
* @param waitForReady if this client should use gRPC's wait for ready semantics. Enabled by default.
* @return this builder.
* @see <a href="https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md">gRPC Wait for Ready
* Semantics</a>
* @param discovery if the endpoint represent a discovery address using dns+srv.
* @return this builder
*/
public ClientBuilder waitForReady(boolean waitForReady) {
this.waitForReady = waitForReady;
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;
public ClientBuilder discovery(boolean discovery) {
this.discovery = discovery;
return this;
}
@ -740,15 +521,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(!endpoints.isEmpty(), "please configure etcd server endpoints before build.");
return new ClientImpl(this);
}
/**
* Returns a copy of this builder
*
* @return a copy of the builder.
* @return a copy of this builder
*/
public ClientBuilder copy() {
try {

View File

@ -0,0 +1,402 @@
/*
* 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.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
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 java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
import io.etcd.jetcd.api.AuthGrpc;
import io.etcd.jetcd.api.AuthenticateRequest;
import io.etcd.jetcd.api.AuthenticateResponse;
import io.etcd.jetcd.common.exception.EtcdExceptionFactory;
import io.etcd.jetcd.resolver.DnsSrvNameResolver;
import io.etcd.jetcd.resolver.IPNameResolver;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.Status;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import io.grpc.stub.AbstractStub;
import io.netty.channel.ChannelOption;
import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
final class ClientConnectionManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientConnectionManager.class);
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 ExecutorService executorService;
private volatile ManagedChannel managedChannel;
private volatile String token;
ClientConnectionManager(ClientBuilder builder) {
this(builder, null);
}
ClientConnectionManager(ClientBuilder builder, ManagedChannel managedChannel) {
this.lock = new Object();
this.builder = builder;
this.managedChannel = managedChannel;
if (builder.executorService() == null) {
this.executorService = Executors.newCachedThreadPool();
} else {
this.executorService = builder.executorService();
}
}
/**
* get token from etcd with name and password.
*
* @param channel channel to etcd
* @param username auth name
* @param password auth password
* @return authResp
*/
private static ListenableFuture<AuthenticateResponse> authenticate(@Nonnull Channel channel, @Nonnull ByteSequence username,
@Nonnull ByteSequence password) {
final ByteString user = username.getByteString();
final ByteString pass = password.getByteString();
checkArgument(!user.isEmpty(), "username can not be empty.");
checkArgument(!pass.isEmpty(), "password can not be empty.");
return AuthGrpc.newFutureStub(channel)
.authenticate(AuthenticateRequest.newBuilder().setNameBytes(user).setPasswordBytes(pass).build());
}
ManagedChannel getChannel() {
if (managedChannel == null) {
synchronized (lock) {
if (managedChannel == null) {
managedChannel = defaultChannelBuilder().build();
}
}
}
return managedChannel;
}
@Nullable
private String getToken(Channel channel) {
if (token == null) {
synchronized (lock) {
if (token == null) {
token = generateToken(channel);
}
}
}
return token;
}
void forceTokenRefresh() {
synchronized (lock) {
token = null;
}
}
private void refreshToken(Channel channel) {
synchronized (lock) {
token = generateToken(channel);
}
}
ByteSequence getNamespace() {
return builder.namespace();
}
ExecutorService getExecutorService() {
return executorService;
}
/**
* create stub with saved channel.
*
* @param supplier the stub supplier
* @param <T> the type of stub
* @return the attached stub
*/
<T extends AbstractStub<T>> T newStub(Function<ManagedChannel, T> supplier) {
return supplier.apply(getChannel()).withWaitForReady();
}
void close() {
synchronized (lock) {
if (managedChannel != null) {
managedChannel.shutdownNow();
}
}
if (builder.executorService() == null) {
executorService.shutdownNow();
}
}
<T extends AbstractStub<T>, R> CompletableFuture<R> withNewChannel(URI endpoint, Function<ManagedChannel, T> stubCustomizer,
Function<T, CompletableFuture<R>> stubConsumer) {
final ManagedChannel channel = defaultChannelBuilder(Collections.singletonList(endpoint)).build();
final T stub = stubCustomizer.apply(channel).withWaitForReady();
try {
return stubConsumer.apply(stub).whenComplete((r, t) -> channel.shutdown());
} catch (Exception e) {
channel.shutdown();
throw EtcdExceptionFactory.toEtcdException(e);
}
}
@VisibleForTesting
protected ManagedChannelBuilder<?> defaultChannelBuilder() {
return defaultChannelBuilder(builder.endpoints());
}
@VisibleForTesting
protected ManagedChannelBuilder<?> defaultChannelBuilder(Collection<URI> endpoints) {
if (endpoints.isEmpty()) {
throw new IllegalArgumentException("At least one endpoint should be provided");
}
final String target;
if (builder.discovery()) {
if (endpoints.size() != 1) {
throw new IllegalArgumentException("When configured for discovery, there should be only a single endpoint");
}
target = String.format(
"%s:///%s",
DnsSrvNameResolver.SCHEME,
Iterables.get(endpoints, 0));
} else {
target = String.format(
"%s://%s/%s",
IPNameResolver.SCHEME,
builder.authority() != null ? builder.authority() : "",
endpoints.stream().map(e -> e.getHost() + ":" + e.getPort()).collect(Collectors.joining(",")));
}
final NettyChannelBuilder channelBuilder = NettyChannelBuilder.forTarget(target);
if (builder.authority() != null) {
channelBuilder.overrideAuthority(builder.authority());
}
if (builder.maxInboundMessageSize() != null) {
channelBuilder.maxInboundMessageSize(builder.maxInboundMessageSize());
}
if (builder.sslContext() != null) {
channelBuilder.negotiationType(NegotiationType.TLS);
channelBuilder.sslContext(builder.sslContext());
} else {
channelBuilder.negotiationType(NegotiationType.PLAINTEXT);
}
if (builder.keepaliveTime() != null) {
channelBuilder.keepAliveTime(builder.keepaliveTime().toMillis(), TimeUnit.MILLISECONDS);
}
if (builder.keepaliveTimeout() != null) {
channelBuilder.keepAliveTimeout(builder.keepaliveTimeout().toMillis(), TimeUnit.MILLISECONDS);
}
if (builder.keepaliveWithoutCalls() != null) {
channelBuilder.keepAliveWithoutCalls(builder.keepaliveWithoutCalls());
}
if (builder.connectTimeout() != null) {
channelBuilder.withOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) builder.connectTimeout().toMillis());
}
if (builder.loadBalancerPolicy() != null) {
channelBuilder.defaultLoadBalancingPolicy(builder.loadBalancerPolicy());
} else {
channelBuilder.defaultLoadBalancingPolicy("pick_first");
}
channelBuilder.intercept(new AuthTokenInterceptor());
if (builder.headers() != null) {
channelBuilder.intercept(new ClientInterceptor() {
@Override
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) {
builder.headers().forEach((BiConsumer<Metadata.Key, Object>) headers::put);
super.start(responseListener, headers);
}
};
}
});
}
if (builder.interceptors() != null) {
channelBuilder.intercept(builder.interceptors());
}
return channelBuilder;
}
/**
* 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, builder.user(), builder.password()).get().getToken();
} catch (InterruptedException ite) {
throw handleInterrupt(ite);
} catch (ExecutionException exee) {
throw toEtcdException(exee);
}
}
return null;
}
/**
* 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 Failsafe.with(retryPolicy).with(executorService)
.getAsyncExecution(execution -> {
CompletableFuture<S> wrappedFuture = new CompletableFuture<>();
ListenableFuture<S> future = task.call();
future.addListener(() -> {
try {
wrappedFuture.complete(future.get());
if (execution.complete(wrappedFuture)) {
// success! but nothing to do here
}
} catch (Exception error) {
if (!execution.retryOn(error)) {
// permanent failure
wrappedFuture.completeExceptionally(error);
}
}
}, executorService);
// note: the actual result value is supplied via execution.complete(..) above
return wrappedFuture;
}).thenCompose(f -> f.thenApply(resultConvert));
}
/**
* AuthTokenInterceptor fills header with Auth token of any rpc calls and
* refreshes token if the rpc results an invalid Auth token error.
*/
private class AuthTokenInterceptor implements ClientInterceptor {
@Override
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) {
String token = getToken(next);
if (token != null) {
headers.put(TOKEN, token);
}
super.start(new 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);
}
};
}
}
}

View File

@ -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

View File

@ -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.
*

View File

@ -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());
}
}

View File

@ -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";
}

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -0,0 +1,169 @@
/*
* 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.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);
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);
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);
}
public Txn txn() {
return TxnImpl.newTxn(
request -> connectionManager.execute(
() -> stub.txn(request),
response -> new TxnResponse(response, namespace), Util::isRetryable),
namespace);
}
}

View File

@ -16,8 +16,6 @@
package io.etcd.jetcd;
import io.etcd.jetcd.support.Util;
/**
* Etcd key value pair.
*/
@ -29,35 +27,26 @@ 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.
* @return the key
*/
public ByteSequence getKey() {
return unprefixedKey;
}
/**
* Returns the value
*
* @return the value.
* @return the value
*/
public ByteSequence getValue() {
return value;
}
/**
* Returns the create revision.
*
* @return the create revision.
*/
public long getCreateRevision() {
@ -65,8 +54,6 @@ public class KeyValue {
}
/**
* Returns the mod revision.
*
* @return the mod revision.
*/
public long getModRevision() {
@ -74,8 +61,6 @@ public class KeyValue {
}
/**
* Returns the version.
*
* @return the version.
*/
public long getVersion() {
@ -83,8 +68,6 @@ public class KeyValue {
}
/**
* Returns the lease.
*
* @return the lease.
*/
public long getLease() {

View File

@ -0,0 +1,352 @@
/*
* 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 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 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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();
}
}
}

View File

@ -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);
}
}

View File

@ -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;
@ -48,7 +55,7 @@ public interface Maintenance extends CloseableClient {
/**
* get all active keyspace alarm.
*
*
* @return the response
*/
CompletableFuture<AlarmResponse> listAlarms();
@ -62,7 +69,7 @@ public interface Maintenance extends CloseableClient {
CompletableFuture<AlarmResponse> alarmDisarm(AlarmMember member);
/**
* Defragment one member of the cluster by its endpoint.
* defragment one member of the cluster by its endpoint.
*
* <p>
* After compacting the keyspace, the backend database may exhibit internal
@ -78,29 +85,29 @@ public interface Maintenance extends CloseableClient {
* To defragment multiple members in the cluster, user need to call defragment
* multiple times with different endpoints.
*
* @param target the etcd server endpoint.
* @return the response result
* @param endpoint the etcd server endpoint.
* @return the response result
*/
CompletableFuture<DefragmentResponse> defragmentMember(String target);
CompletableFuture<DefragmentResponse> defragmentMember(URI endpoint);
/**
* get the status of a member by its endpoint.
*
* @param target the etcd server endpoint.
* @return the response result
* @param endpoint the etcd server endpoint.
* @return the response result
*/
CompletableFuture<StatusResponse> statusMember(String target);
CompletableFuture<StatusResponse> statusMember(URI endpoint);
/**
* 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
* is non-zero, the hash is computed on all keys at or below the given revision.
*
* @param target the etcd server endpoint.
* @param rev the revision
* @return the response result
* @param endpoint the etcd server endpoint.
* @param rev the revision
* @return the response result
*/
CompletableFuture<HashKVResponse> hashKV(String target, long rev);
CompletableFuture<HashKVResponse> hashKV(URI endpoint, long rev);
/**
* retrieves backend snapshot.

View File

@ -0,0 +1,208 @@
/*
* 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);
}
/**
* get all active keyspace alarm.
*
* @return alarm list
*/
@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());
}
/**
* disarms a given alarm.
*
* @param member the alarm
* @return the response result
*/
@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());
}
/**
* defragment one member of the cluster.
*
* <p>
* After compacting the keyspace, the backend database may exhibit internal
* fragmentation. Any internal fragmentation is space that is free to use
* by the backend but still consumes storage space. The process of
* defragmentation releases this storage space back to the file system.
* Defragmentation is issued on a per-member so that cluster-wide latency
* spikes may be avoided.
*
* <p>
* Defragment is an expensive operation. User should avoid defragmenting
* multiple members at the same time.
* To defragment multiple members in the cluster, user need to call defragment
* multiple times with different endpoints.
*/
@Override
public CompletableFuture<DefragmentResponse> defragmentMember(URI endpoint) {
return this.connectionManager.withNewChannel(
endpoint,
MaintenanceGrpc::newFutureStub,
stub -> Util.toCompletableFuture(
stub.defragment(DefragmentRequest.getDefaultInstance()),
DefragmentResponse::new,
this.connectionManager.getExecutorService()));
}
/**
* get the status of one member.
*/
@Override
public CompletableFuture<StatusResponse> statusMember(URI endpoint) {
return this.connectionManager.withNewChannel(
endpoint,
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(
endpoint,
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();
}
});
}
}

View File

@ -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);
}
}
}

View File

@ -22,39 +22,29 @@ package io.etcd.jetcd;
public interface Response {
/**
* Returns the response header
*
* @return the header.
* @return the response header
*/
Header getHeader();
interface Header {
/**
* Returns the cluster id
*
* @return the cluster id.
* @return the cluster id
*/
long getClusterId();
/**
* Returns the member id
*
* @return the member id.
* @return the member id
*/
long getMemberId();
/**
* Returns the revision id
*
* @return the revision.
* @return the revision id
*/
long getRevision();
/**
* Returns the raft term
*
* @return theraft term.
* @return the raft term
*/
long getRaftTerm();
}

View File

@ -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>
*/

View File

@ -0,0 +1,144 @@
/*
* 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.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.List;
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 com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.stub.AbstractStub;
import io.grpc.stub.MetadataUtils;
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.toEtcdException;
public final class Util {
private Util() {
}
public static List<URI> toURIs(Collection<String> uris) {
return uris.stream().map(uri -> {
try {
return new URI(uri);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid endpoint URI: " + uri, e);
}
}).collect(Collectors.toList());
}
/**
* 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;
}
static boolean isRetryable(Throwable e) {
return isInvalidTokenError(Status.fromThrowable(e));
}
static boolean isInvalidTokenError(Status status) {
return status.getCode() == Status.Code.UNAUTHENTICATED
&& "etcdserver: invalid auth token".equals(status.getDescription());
}
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 : namespace.getByteString().concat(key);
}
public static ByteString prefixNamespaceToRangeEnd(ByteString end, ByteSequence namespace) {
if (namespace.isEmpty()) {
return end;
}
if (end.size() == 1 && end.toByteArray()[0] == 0) {
// range end is '\0', calculate the prefixed range end by (key + 1)
byte[] prefixedEndArray = namespace.getByteString().toByteArray();
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 = new byte[] { 0 };
}
return ByteString.copyFrom(prefixedEndArray);
} else {
return namespace.getByteString().concat(end);
}
}
public static ByteString unprefixNamespace(ByteString key, ByteSequence namespace) {
return namespace.isEmpty() ? key : key.substring(namespace.size());
}
static <T extends AbstractStub<T>> T applyRequireLeader(boolean requireLeader, T stub) {
if (!requireLeader) {
return stub;
}
final Metadata md = new Metadata();
md.put(Constants.REQUIRE_LEADER_KEY, Constants.REQUIRE_LEADER_VALUE);
return MetadataUtils.attachHeaders(stub, md);
}
public static boolean isHaltError(final Status status) {
return status.getCode() != Status.Code.UNAVAILABLE && status.getCode() != Status.Code.INTERNAL;
}
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());
}
}

View File

@ -53,8 +53,6 @@ public interface Watch extends CloseableClient {
}
/**
* Watch key.
*
* @param key key to be watched on.
* @param onNext the on next consumer
* @return this watcher
@ -64,8 +62,6 @@ public interface Watch extends CloseableClient {
}
/**
* Watch key.
*
* @param key key to be watched on.
* @param onNext the on next consumer
* @param onError the on error consumer
@ -76,8 +72,6 @@ public interface Watch extends CloseableClient {
}
/**
* Watch key.
*
* @param key key to be watched on.
* @param onNext the on next consumer
* @param onError the on error consumer
@ -89,8 +83,6 @@ public interface Watch extends CloseableClient {
}
/**
* Watch key.
*
* @param key key to be watched on.
* @param onNext the on next consumer
* @param onCompleted the on completion consumer
@ -102,7 +94,6 @@ public interface Watch extends CloseableClient {
}
/**
* Watch key with option.
*
* @param key key to be watched on.
* @param option the options
@ -114,8 +105,6 @@ public interface Watch extends CloseableClient {
}
/**
* Watch key with option.
*
* @param key key to be watched on.
* @param option the options
* @param onNext the on next consumer
@ -127,8 +116,6 @@ public interface Watch extends CloseableClient {
}
/**
* Watch key with option.
*
* @param key key to be watched on.
* @param option the options
* @param onNext the on next consumer
@ -141,8 +128,6 @@ public interface Watch extends CloseableClient {
}
/**
* Watch key with option.
*
* @param key key to be watched on.
* @param option the options
* @param onNext the on next consumer
@ -155,11 +140,6 @@ public interface Watch extends CloseableClient {
return watch(key, option, listener(onNext, onError, onCompleted));
}
/**
* Requests the latest revision processed for all watcher instances
*/
void requestProgress();
static Listener listener(Consumer<WatchResponse> onNext) {
return listener(onNext, t -> {
}, () -> {
@ -225,15 +205,5 @@ 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
*/
void requestProgress();
}
}

View File

@ -14,40 +14,31 @@
* 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.WatchProgressRequest;
import io.etcd.jetcd.api.WatchRequest;
import io.etcd.jetcd.api.WatchResponse;
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 com.google.common.base.Strings;
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 io.etcd.jetcd.api.WatchCancelRequest;
import io.etcd.jetcd.api.WatchCreateRequest;
import io.etcd.jetcd.api.WatchGrpc;
import io.etcd.jetcd.api.WatchRequest;
import io.etcd.jetcd.api.WatchResponse;
import io.etcd.jetcd.common.exception.ErrorCode;
import io.etcd.jetcd.common.exception.EtcdException;
import io.etcd.jetcd.options.WatchOption;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.newClosedWatchClientException;
import static io.etcd.jetcd.common.exception.EtcdExceptionFactory.newCompactedException;
@ -57,24 +48,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();
@ -108,23 +97,13 @@ final class WatchImpl extends Impl implements Watch {
}
}
@Override
public void requestProgress() {
if (!closed.get()) {
synchronized (this.lock) {
watchers.forEach(Watcher::requestProgress);
}
}
}
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 +113,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 +124,7 @@ final class WatchImpl extends Impl implements Watch {
//
// ************************
@Override
public boolean isClosed() {
boolean isClosed() {
return this.closed.get() || WatchImpl.this.closed.get();
}
@ -156,24 +133,17 @@ 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()) {
ByteSequence endKey = OptionsUtil.prefixEndOf(key);
builder.setRangeEnd(Util.prefixNamespaceToRangeEnd(endKey, namespace));
}
if (option.isNoDelete()) {
builder.addFilters(WatchCreateRequest.FilterType.NODELETE);
}
@ -182,15 +152,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 +163,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;
}
}
@ -222,21 +186,14 @@ final class WatchImpl extends Impl implements Watch {
}
}
@Override
public void requestProgress() {
if (!closed.get() && wstream.get() != null) {
WatchProgressRequest watchProgressRequest = WatchProgressRequest.newBuilder().build();
wstream.get().write(WatchRequest.newBuilder().setProgressRequest(watchProgressRequest).build());
}
}
// ************************
//
// StreamObserver
//
// ************************
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 +202,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.forceTokenRefresh();
Status error = Status.Code.CANCELLED.toStatus().withDescription(response.getCancelReason());
handleError(toEtcdException(error), true);
} else if (response.getCreated()) {
@ -263,11 +218,8 @@ final class WatchImpl extends Impl implements Watch {
return;
}
revision = Math.max(revision, response.getHeader().getRevision());
revision = response.getHeader().getRevision();
id = response.getWatchId();
if (option.isCreatedNotify()) {
listener.onNext(new io.etcd.jetcd.watch.WatchResponse(response));
}
} else if (response.getCanceled()) {
//
@ -286,10 +238,7 @@ final class WatchImpl extends Impl implements Watch {
error = newEtcdException(ErrorCode.FAILED_PRECONDITION, reason);
}
handleError(toEtcdException(error), false);
} else if (io.etcd.jetcd.watch.WatchResponse.isProgressNotify(response)) {
listener.onNext(new io.etcd.jetcd.watch.WatchResponse(response));
revision = Math.max(revision, response.getHeader().getRevision());
listener.onError(error);
} else if (response.getEventsCount() == 0 && option.isProgressNotify()) {
//
@ -315,11 +264,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 +277,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 +290,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 +305,9 @@ final class WatchImpl extends Impl implements Watch {
}
}, executor);
}
@Override
public void onCompleted() {
}
}
}

View File

@ -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> {

View File

@ -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> {

View File

@ -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> {

View File

@ -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> {

View File

@ -19,12 +19,12 @@ 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;
import io.etcd.jetcd.auth.Permission.Type;
/**
* 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> {
@ -42,16 +42,16 @@ public class AuthRoleGetResponse extends AbstractResponse<io.etcd.jetcd.api.Auth
Permission.Type type;
switch (perm.getPermType()) {
case READ:
type = Permission.Type.READ;
type = Type.READ;
break;
case WRITE:
type = Permission.Type.WRITE;
type = Type.WRITE;
break;
case READWRITE:
type = Permission.Type.READWRITE;
type = Type.READWRITE;
break;
default:
type = Permission.Type.UNRECOGNIZED;
type = Type.UNRECOGNIZED;
}
return new Permission(type, key, rangeEnd);

View File

@ -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> {

View File

@ -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
@ -32,9 +32,7 @@ public class AuthRoleListResponse extends AbstractResponse<io.etcd.jetcd.api.Aut
}
/**
* Returns a list of roles.
*
* @return the roles.
* @return a list of roles.
*/
public List<String> getRoles() {
return getResponse().getRolesList();

View File

@ -16,10 +16,12 @@
package io.etcd.jetcd.auth;
import io.etcd.jetcd.impl.AbstractResponse;
import io.etcd.jetcd.AbstractResponse;
import io.etcd.jetcd.ByteSequence;
/**
* AuthRoleRevokePermissionResponse
* AuthRoleRevokePermissionResponse returned by {@link io.etcd.jetcd.Auth#roleRevokePermission(ByteSequence,
* ByteSequence, ByteSequence)} contains a header.
*/
public class AuthRoleRevokePermissionResponse extends AbstractResponse<io.etcd.jetcd.api.AuthRoleRevokePermissionResponse> {

View File

@ -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> {

View File

@ -16,10 +16,12 @@
package io.etcd.jetcd.auth;
import io.etcd.jetcd.impl.AbstractResponse;
import io.etcd.jetcd.AbstractResponse;
import io.etcd.jetcd.ByteSequence;
/**
* AuthUserChangePasswordResponse
* AuthUserChangePasswordResponse returned by {@link io.etcd.jetcd.Auth#userChangePassword(ByteSequence,
* ByteSequence)} contains a header.
*/
public class AuthUserChangePasswordResponse extends AbstractResponse<io.etcd.jetcd.api.AuthUserChangePasswordResponse> {

View File

@ -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> {

View File

@ -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> {
@ -33,9 +32,7 @@ public class AuthUserGetResponse extends AbstractResponse<io.etcd.jetcd.api.Auth
}
/**
* Returns a list of roles.
*
* @return the roles.
* @return a list of roles.
*/
public List<String> getRoles() {
return getResponse().getRolesList();

View File

@ -16,10 +16,12 @@
package io.etcd.jetcd.auth;
import io.etcd.jetcd.impl.AbstractResponse;
import io.etcd.jetcd.AbstractResponse;
import io.etcd.jetcd.ByteSequence;
/**
* AuthUserGrantRoleResponse
* AuthUserGrantRoleResponse returned by {@link io.etcd.jetcd.Auth#userGrantRole(ByteSequence,
* ByteSequence)} contains a header.
*/
public class AuthUserGrantRoleResponse extends AbstractResponse<io.etcd.jetcd.api.AuthUserGrantRoleResponse> {

View File

@ -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> {
@ -32,9 +31,7 @@ public class AuthUserListResponse extends AbstractResponse<io.etcd.jetcd.api.Aut
}
/**
* Returns a list of users.
*
* @return the users.
* @return a list of users.
*/
public List<String> getUsers() {
return getResponse().getUsersList();

View File

@ -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> {

View File

@ -38,9 +38,7 @@ public class Permission {
}
/**
* Returns the type of Permission: READ, WRITE, READWRITE, or UNRECOGNIZED.
*
* @return the permission type.
* @return the type of Permission: READ, WRITE, READWRITE, or UNRECOGNIZED.
*/
public Type getPermType() {
return permType;

View File

@ -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 {
@ -30,48 +30,31 @@ public class Member {
}
/**
* Returns the member ID for this member.
*
* @return the id.
* @return the member ID for this member.
*/
public long getId() {
return member.getID();
}
/**
* Returns the human-readable name of the member, ff the member is not started, the name will be an empty string.
*
* @return the name.
* @return the human-readable name of the member, ff the member is not started, the name will be an empty string.
*/
public String getName() {
return member.getName();
}
/**
* Returns the list of URLs the member exposes to the cluster for communication.
*
* @return the peer url
* @return the list of URLs the member exposes to the cluster for communication.
*/
public List<URI> getPeerURIs() {
return Util.toURIs(member.getPeerURLsList());
}
/**
* 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.
* @return list of URLs the member exposes to clients for communication, if the member is not started, clientURLs will
* be empty.
*/
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();
}
}

View File

@ -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> {
@ -36,18 +35,14 @@ public class MemberAddResponse extends AbstractResponse<io.etcd.jetcd.api.Member
}
/**
* Returns the member information for the added member.
*
* @return the member information.
* @return the member information for the added member.
*/
public Member getMember() {
return member;
}
/**
* Returns a list of all members after adding the new member.
*
* @return the list of members.
* @return a list of all members after adding the new member.
*/
public synchronized List<Member> getMembers() {
if (members == null) {

View File

@ -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()}
@ -34,9 +34,7 @@ public class MemberListResponse extends AbstractResponse<io.etcd.jetcd.api.Membe
}
/**
* Returns a list of members. empty list if none.
*
* @return the list of members.
* @return a list of members. empty list if none.
*/
public synchronized List<Member> getMembers() {
if (members == null) {

View File

@ -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> {
@ -34,9 +33,7 @@ public class MemberRemoveResponse extends AbstractResponse<io.etcd.jetcd.api.Mem
}
/**
* Returns a list of all members after removing the member.
*
* @return the list of members.
* @return a list of all members after removing the member.
*/
public synchronized List<Member> getMembers() {
if (members == null) {

View File

@ -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> {
@ -34,9 +33,7 @@ public class MemberUpdateResponse extends AbstractResponse<io.etcd.jetcd.api.Mem
}
/**
* Return a list of all members after updating the member.
*
* @return the list of members.
* @return a list of all members after updating the member.
*/
public synchronized List<Member> getMembers() {
if (members == null) {

View File

@ -22,14 +22,13 @@ 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.
*/
static List<Member> toMembers(List<io.etcd.jetcd.api.Member> members) {
return members.stream().map(Member::new).collect(Collectors.toList());
static List<io.etcd.jetcd.cluster.Member> toMembers(List<io.etcd.jetcd.api.Member> members) {
return members.stream().map(io.etcd.jetcd.cluster.Member::new).collect(Collectors.toList());
}
}

Some files were not shown because too many files have changed in this diff Show More