diff --git a/.gitignore b/.gitignore
index 60883492f3127..04f8feed0ad70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,3 +47,7 @@ tests/venv
docs/generated/
.release-settings.json
+
+kafkatest.egg-info/
+systest/
+*.swp
diff --git a/README.md b/README.md
index 9371e48216986..18cb03e988048 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,9 @@ See our [web site](http://kafka.apache.org) for details on the project.
You need to have [Gradle](http://www.gradle.org/installation) and [Java](http://www.oracle.com/technetwork/java/javase/downloads/index.html) installed.
-Kafka requires Gradle 3.0 or higher.
+Kafka requires Gradle 4.5 or higher.
-Java 7 should be used for building in order to support both Java 7 and Java 8 at runtime.
+Java 8 should be used for building in order to support both Java 8 and Java 10 at runtime.
### First bootstrap and download the wrapper ###
cd kafka_source_dir
@@ -85,8 +85,6 @@ You can pass either the major version (eg 2.11) or the full version (eg 2.11.12)
./gradlew -PscalaVersion=2.11 test
./gradlew -PscalaVersion=2.11 releaseTarGz
-Scala 2.12.x requires Java 8.
-
### Running a task for a specific project ###
This is for `core`, `examples` and `clients`
diff --git a/TROGDOR.md b/TROGDOR.md
new file mode 100644
index 0000000000000..b8c94d4bc7ce2
--- /dev/null
+++ b/TROGDOR.md
@@ -0,0 +1,154 @@
+Trogdor
+========================================
+Trogdor is a test framework for Apache Kafka.
+
+Trogdor can run benchmarks and other workloads. Trogdor can also inject faults in order to stress test the system.
+
+Quickstart
+=========================================================
+First, we want to start a single-node Kafka cluster with a ZooKeeper and a broker.
+
+Running ZooKeeper:
+
+ > ./bin/zookeeper-server-start.sh ./config/zookeeper.properties &> /tmp/zookeeper.log &
+
+Running Kafka:
+
+ > ./bin/kafka-server-start.sh ./config/server.properties &> /tmp/kafka.log &
+
+Then, we want to run a Trogdor Agent, plus a Trogdor broker.
+
+To run the Trogdor Agent:
+
+ > ./bin/trogdor.sh agent -c ./config/trogdor.conf -n node0 &> /tmp/trogdor-agent.log &
+
+To run the Trogdor Coordinator:
+
+ > ./bin/trogdor.sh coordinator -c ./config/trogdor.conf -n node0 &> /tmp/trogdor-coordinator.log &
+
+Let's confirm that all of the daemons are running:
+
+ > jps
+ 116212 Coordinator
+ 115188 QuorumPeerMain
+ 116571 Jps
+ 115420 Kafka
+ 115694 Agent
+
+Now, we can submit a test job to Trogdor. Here's an example of a short bash script which makes it easier.
+
+ > ./tests/bin/trogdor-run-produce-bench.sh
+ [2018-04-12 10:32:04,055] DEBUG Sending POST with input {"id":"produce_bench_22137","spec":{"class":"org.apache.kafka.trogdor.workload.ProduceBenchSpec","startMs":0,"durationMs":10000000,"producerNode":"node0","bootstrapServers":"localhost:9092","targetMessagesPerSec":10,"maxMessages":100,"keyGenerator":{"type":"sequential","size":4,"startOffset":0},"valueGenerator":{"type":"constant","size":512,"value":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="},"totalTopics":10,"activeTopics":5,"topicPrefix":"foo","replicationFactor":1,"classLoader":{},"numPartitions":1}} to http://localhost:8889/coordinator/task/create (org.apache.kafka.trogdor.coordinator.CoordinatorClient)
+ Created task.
+ $TASK_ID = produce_bench_20462
+
+To get the test results, we run --show-tasks:
+
+ ./tests/bin/trogdor.sh client --show-tasks localhost:8889
+ Got coordinator tasks: {
+ "tasks" : {
+ "produce_bench_20462" : {
+ "state" : "DONE",
+ "spec" : {
+ "class" : "org.apache.kafka.trogdor.workload.ProduceBenchSpec",
+ "startMs" : 0,
+ "durationMs" : 10000000,
+ "producerNode" : "node0",
+ "bootstrapServers" : "localhost:9092",
+ "targetMessagesPerSec" : 10,
+ "maxMessages" : 100,
+ "keyGenerator" : {
+ "type" : "sequential",
+ "size" : 4,
+ "startOffset" : 0
+ },
+ "valueGenerator" : {
+ "type" : "constant",
+ "size" : 512,
+ "value" : "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
+ },
+ "totalTopics" : 10,
+ "activeTopics" : 5,
+ "topicPrefix" : "foo",
+ "replicationFactor" : 1,
+ "classLoader" : { },
+ "numPartitions" : 1
+ },
+ "startedMs" : 1523552769850,
+ "doneMs" : 1523552780878,
+ "cancelled" : false,
+ "status" : {
+ "totalSent" : 500,
+ "averageLatencyMs" : 4.972,
+ "p50LatencyMs" : 4,
+ "p95LatencyMs" : 6,
+ "p99LatencyMs" : 12
+ }
+ }
+ }
+ }
+
+Trogdor Architecture
+========================================
+Trogdor has a single coordinator process which manages multiple agent processes. Each agent process is responsible for a single cluster node.
+
+The Trogdor coordinator manages tasks. A task is anything we might want to do on a cluster, such as running a benchmark, injecting a fault, or running a workload. In order to implement each task, the coordinator creates workers on one or more agent nodes.
+
+The Trogdor agent process implements the tasks. For example, when running a workload, the agent process is the process which produces and consumes messages.
+
+Both the coordinator and the agent expose a REST interface that accepts objects serialized via JSON. There is also a command-line program which makes it easy to send messages to either one without manually crafting the JSON message body.
+
+All Trogdor RPCs are idempotent except the shutdown requests. Sending an idempotent RPC twice in a row has the same effect as sending the RPC once.
+
+Tasks
+========================================
+Tasks are described by specifications containing:
+
+* A "class" field describing the task type. This contains a full Java class name.
+* A "startMs" field describing when the task should start. This is given in terms of milliseconds since the UNIX epoch.
+* A "durationMs" field describing how long the task should last. This is given in terms of milliseconds.
+* Other fields which are task-specific.
+
+The task specification is usually written as JSON. For example, this task specification describes a network partition between nodes 1 and 2, and 3:
+
+ {
+ "class": "org.apache.kafka.trogdor.fault.NetworkPartitionFaultSpec",
+ "startMs": 1000,
+ "durationMs": 30000,
+ "partitions": [["node1", "node2"], ["node3"]]
+ }
+
+Tasks are submitted to the coordinator. Once the coordinator determines that it is time for the task to start, it creates workers on agent processes. The workers run until the task is done.
+
+Task specifications are immutable; they do not change after the task has been created.
+
+Tasks can be in several states:
+* PENDING, when task is waiting to execute,
+* RUNNING, when the task is running,
+* STOPPING, when the task is in the process of stopping,
+* DONE, when the task is done.
+
+Tasks that are DONE also have an error field which will be set if the task failed.
+
+Workloads
+========================================
+Trogdor can run several workloads. Workloads perform operations on the cluster and measure their performance. Workloads fail when the operations cannot be performed.
+
+### ProduceBench
+ProduceBench starts a Kafka producer on a single agent node, producing to several partitions. The workload measures the average produce latency, as well as the median, 95th percentile, and 99th percentile latency.
+
+### RoundTripWorkload
+RoundTripWorkload tests both production and consumption. The workload starts a Kafka producer and consumer on a single node. The consumer will read back the messages that were produced by the producer.
+
+### ConsumeBench
+ConsumeBench starts a Kafka consumer on a single agent node. The workload measures the average produce latency, as well as the median, 95th percentile, and 99th percentile latency.
+
+Faults
+========================================
+Trogdor can run several faults which deliberately break something in the cluster.
+
+### ProcessStopFault
+ProcessStopFault stops a process by sending it a SIGSTOP signal. When the fault ends, the process is resumed with SIGCONT.
+
+### NetworkPartitionFault
+NetworkPartitionFault sets up an artificial network partition between one or more sets of nodes. Currently, this is implemented using iptables. The iptables rules are set up on the outbound traffic from the affected nodes. Therefore, the affected nodes should still be reachable from outside the cluster.
diff --git a/Vagrantfile b/Vagrantfile
index 2179a3c37ecb1..363607691fd0d 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -40,7 +40,7 @@ ec2_keypair_file = nil
ec2_region = "us-east-1"
ec2_az = nil # Uses set by AWS
-ec2_ami = "ami-9eaa1cf6"
+ec2_ami = "ami-905730e8"
ec2_instance_type = "m3.medium"
ec2_user = "ubuntu"
ec2_instance_name_prefix = "kafka-vagrant"
@@ -80,7 +80,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# share to a temporary location and the provisioning scripts symlink data
# to the right location.
override.cache.enable :generic, {
- "oracle-jdk7" => { cache_dir: "/tmp/oracle-jdk7-installer-cache" },
+ "oracle-jdk8" => { cache_dir: "/tmp/oracle-jdk8-installer-cache" },
}
end
end
@@ -141,7 +141,10 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
def name_node(node, name, ec2_instance_name_prefix)
node.vm.hostname = name
node.vm.provider :aws do |aws|
- aws.tags = { 'Name' => ec2_instance_name_prefix + "-" + Socket.gethostname + "-" + name }
+ aws.tags = {
+ 'Name' => ec2_instance_name_prefix + "-" + Socket.gethostname + "-" + name,
+ 'JenkinsBuildUrl' => ENV['BUILD_URL']
+ }
end
end
diff --git a/bin/kafka-dump-log.sh b/bin/kafka-dump-log.sh
new file mode 100755
index 0000000000000..a97ea7d3d9f8c
--- /dev/null
+++ b/bin/kafka-dump-log.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+# 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.
+
+exec $(dirname $0)/kafka-run-class.sh kafka.tools.DumpLogSegments "$@"
diff --git a/bin/kafka-run-class.sh b/bin/kafka-run-class.sh
index 6676d0d2156b7..0ea8b83194684 100755
--- a/bin/kafka-run-class.sh
+++ b/bin/kafka-run-class.sh
@@ -69,28 +69,50 @@ do
fi
done
-for file in "$base_dir"/clients/build/libs/kafka-clients*.jar;
-do
- if should_include_file "$file"; then
- CLASSPATH="$CLASSPATH":"$file"
- fi
-done
+if [ -z "$UPGRADE_KAFKA_STREAMS_TEST_VERSION" ]; then
+ clients_lib_dir=$(dirname $0)/../clients/build/libs
+ streams_lib_dir=$(dirname $0)/../streams/build/libs
+ rocksdb_lib_dir=$(dirname $0)/../streams/build/dependant-libs-${SCALA_VERSION}
+else
+ clients_lib_dir=/opt/kafka-$UPGRADE_KAFKA_STREAMS_TEST_VERSION/libs
+ streams_lib_dir=$clients_lib_dir
+ rocksdb_lib_dir=$streams_lib_dir
+fi
+
-for file in "$base_dir"/streams/build/libs/kafka-streams*.jar;
+for file in "$clients_lib_dir"/kafka-clients*.jar;
do
if should_include_file "$file"; then
CLASSPATH="$CLASSPATH":"$file"
fi
done
-for file in "$base_dir"/streams/examples/build/libs/kafka-streams-examples*.jar;
+for file in "$streams_lib_dir"/kafka-streams*.jar;
do
if should_include_file "$file"; then
CLASSPATH="$CLASSPATH":"$file"
fi
done
-for file in "$base_dir"/streams/build/dependant-libs-${SCALA_VERSION}/rocksdb*.jar;
+if [ -z "$UPGRADE_KAFKA_STREAMS_TEST_VERSION" ]; then
+ for file in "$base_dir"/streams/examples/build/libs/kafka-streams-examples*.jar;
+ do
+ if should_include_file "$file"; then
+ CLASSPATH="$CLASSPATH":"$file"
+ fi
+ done
+else
+ VERSION_NO_DOTS=`echo $UPGRADE_KAFKA_STREAMS_TEST_VERSION | sed 's/\.//g'`
+ SHORT_VERSION_NO_DOTS=${VERSION_NO_DOTS:0:((${#VERSION_NO_DOTS} - 1))} # remove last char, ie, bug-fix number
+ for file in "$base_dir"/streams/upgrade-system-tests-$SHORT_VERSION_NO_DOTS/build/libs/kafka-streams-upgrade-system-tests*.jar;
+ do
+ if should_include_file "$file"; then
+ CLASSPATH="$CLASSPATH":"$file"
+ fi
+ done
+fi
+
+for file in "$rocksdb_lib_dir"/rocksdb*.jar;
do
CLASSPATH="$CLASSPATH":"$file"
done
@@ -137,7 +159,7 @@ done
shopt -u nullglob
if [ -z "$CLASSPATH" ] ; then
- echo "Classpath is empty. Please build the project first e.g. by running './gradlew jar -Pscala_version=$SCALA_VERSION'"
+ echo "Classpath is empty. Please build the project first e.g. by running './gradlew jar -PscalaVersion=$SCALA_VERSION'"
exit 1
fi
@@ -216,6 +238,12 @@ if [ -z "$KAFKA_JVM_PERFORMANCE_OPTS" ]; then
KAFKA_JVM_PERFORMANCE_OPTS="-server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent -Djava.awt.headless=true"
fi
+# version option
+for args in "$@" ; do
+ if [ "$args" = "--version" ]; then
+ exec $JAVA $KAFKA_HEAP_OPTS $KAFKA_JVM_PERFORMANCE_OPTS $KAFKA_GC_LOG_OPTS $KAFKA_JMX_OPTS $KAFKA_LOG4J_OPTS -cp $CLASSPATH $KAFKA_OPTS "kafka.utils.VersionInfo"
+ fi
+done
while [ $# -gt 0 ]; do
COMMAND=$1
@@ -246,9 +274,16 @@ GC_FILE_SUFFIX='-gc.log'
GC_LOG_FILE_NAME=''
if [ "x$GC_LOG_ENABLED" = "xtrue" ]; then
GC_LOG_FILE_NAME=$DAEMON_NAME$GC_FILE_SUFFIX
- # the first segment of the version number, which is '1' for releases before Java 9
+
+ # The first segment of the version number, which is '1' for releases before Java 9
# it then becomes '9', '10', ...
- JAVA_MAJOR_VERSION=$($JAVA -version 2>&1 | sed -E -n 's/.* version "([^.-]*).*"/\1/p')
+ # Some examples of the first line of `java --version`:
+ # 8 -> java version "1.8.0_152"
+ # 9.0.4 -> java version "9.0.4"
+ # 10 -> java version "10" 2018-03-20
+ # 10.0.1 -> java version "10.0.1" 2018-04-17
+ # We need to match to the end of the line to prevent sed from printing the characters that do not match
+ JAVA_MAJOR_VERSION=$($JAVA -version 2>&1 | sed -E -n 's/.* version "([0-9]*).*$/\1/p')
if [[ "$JAVA_MAJOR_VERSION" -ge "9" ]] ; then
KAFKA_GC_LOG_OPTS="-Xlog:gc*:file=$LOG_DIR/$GC_LOG_FILE_NAME:time,tags:filecount=10,filesize=102400"
else
diff --git a/bin/windows/kafka-consumer-offset-checker.bat b/bin/windows/kafka-dump-log.bat
similarity index 92%
rename from bin/windows/kafka-consumer-offset-checker.bat
rename to bin/windows/kafka-dump-log.bat
index 2baa1b84061a0..3a1473dc61bc7 100644
--- a/bin/windows/kafka-consumer-offset-checker.bat
+++ b/bin/windows/kafka-dump-log.bat
@@ -14,4 +14,4 @@ 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.
-"%~dp0kafka-run-class.bat" kafka.tools.ConsumerOffsetChecker %*
+"%~dp0kafka-run-class.bat" kafka.tools.DumpLogSegments %*
diff --git a/build.gradle b/build.gradle
index 5e4c35643c2a1..4f3fd770b78a3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -26,9 +26,9 @@ buildscript {
// For Apache Rat plugin to ignore non-Git files
classpath "org.ajoberstar:grgit:1.9.3"
classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
- classpath 'org.scoverage:gradle-scoverage:2.1.0'
- classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2'
- classpath 'org.owasp:dependency-check-gradle:3.0.2'
+ classpath 'org.scoverage:gradle-scoverage:2.3.0'
+ classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
+ classpath 'org.owasp:dependency-check-gradle:3.1.2'
}
}
@@ -41,7 +41,6 @@ allprojects {
}
apply plugin: 'idea'
- apply plugin: "jacoco"
apply plugin: 'org.owasp.dependencycheck'
apply plugin: 'com.github.ben-manes.versions'
@@ -68,24 +67,19 @@ allprojects {
}
}
- if (JavaVersion.current().isJava8Compatible()) {
- tasks.withType(Javadoc) {
- // disable the crazy super-strict doclint tool in Java 8
- // noinspection SpellCheckingInspection
- options.addStringOption('Xdoclint:none', '-quiet')
- }
+ tasks.withType(Javadoc) {
+ // disable the crazy super-strict doclint tool in Java 8
+ // noinspection SpellCheckingInspection
+ options.addStringOption('Xdoclint:none', '-quiet')
}
}
ext {
- gradleVersion = "4.5.1"
+ gradleVersion = "4.7"
+ minJavaVersion = "8"
buildVersionFileName = "kafka-version.properties"
- maxPermSizeArgs = []
- if (!JavaVersion.current().isJava8Compatible())
- maxPermSizeArgs += '-XX:MaxPermSize=512m'
-
userMaxForks = project.hasProperty('maxParallelForks') ? maxParallelForks.toInteger() : null
skipSigning = project.hasProperty('skipSigning') && skipSigning.toBoolean()
@@ -121,11 +115,13 @@ if (new File('.git').exists()) {
'PULL_REQUEST_TEMPLATE.md',
'gradlew',
'gradlew.bat',
+ 'TROGDOR.md',
'**/README.md',
'**/id_rsa',
'**/id_rsa.pub',
'checkstyle/suppressions.xml',
- 'streams/quickstart/java/src/test/resources/projects/basic/goal.txt'
+ 'streams/quickstart/java/src/test/resources/projects/basic/goal.txt',
+ 'streams/streams-scala/logs/*'
])
}
}
@@ -144,20 +140,17 @@ subprojects {
if (!JavaVersion.current().isJava9Compatible())
apply plugin: 'findbugs'
- sourceCompatibility = 1.7
- targetCompatibility = 1.7
+ sourceCompatibility = minJavaVersion
+ targetCompatibility = minJavaVersion
compileJava {
options.encoding = 'UTF-8'
- options.compilerArgs << "-Xlint:deprecation"
- // -Xlint:unchecked is too buggy in Java 7, so we only enable for Java 8 or higher
- if (JavaVersion.current().isJava8Compatible())
- options.compilerArgs << "-Xlint:unchecked"
+ options.compilerArgs << "-Xlint:deprecation,unchecked"
// --release is the recommended way to select the target release, but it's only supported in Java 9 so we also
// set --source and --target via `sourceCompatibility` and `targetCompatibility`. If/when Gradle supports `--release`
// natively (https://github.com/gradle/gradle/issues/2510), we should switch to that.
if (JavaVersion.current().isJava9Compatible())
- options.compilerArgs << "--release" << "7"
+ options.compilerArgs << "--release" << minJavaVersion
}
uploadArchives {
@@ -201,7 +194,6 @@ subprojects {
minHeapSize = "256m"
maxHeapSize = "2048m"
- jvmArgs = maxPermSizeArgs
testLogging {
events = userTestLoggingEvents ?: testLoggingEvents
@@ -216,7 +208,6 @@ subprojects {
minHeapSize = "256m"
maxHeapSize = "2048m"
- jvmArgs = maxPermSizeArgs
testLogging {
events = userTestLoggingEvents ?: testLoggingEvents
@@ -235,7 +226,6 @@ subprojects {
minHeapSize = "256m"
maxHeapSize = "2048m"
- jvmArgs = maxPermSizeArgs
testLogging {
events = userTestLoggingEvents ?: testLoggingEvents
@@ -342,7 +332,7 @@ subprojects {
"-Xlint:private-shadow",
"-Xlint:stars-align",
"-Xlint:type-parameter-shadow",
- "-Xlint:unsound-match",
+ "-Xlint:unsound-match"
]
if (versions.baseScala != '2.11') {
@@ -354,15 +344,14 @@ subprojects {
configure(scalaCompileOptions.forkOptions) {
memoryMaximumSize = '1g'
- jvmArgs = ['-Xss2m'] + maxPermSizeArgs
+ jvmArgs = ['-Xss2m']
}
}
checkstyle {
configFile = new File(rootDir, "checkstyle/checkstyle.xml")
configProperties = [importControlFile: "$rootDir/checkstyle/import-control.xml"]
- // version 7.x requires Java 8
- toolVersion = '6.19'
+ toolVersion = '8.10'
}
test.dependsOn('checkstyleMain', 'checkstyleTest')
@@ -384,9 +373,10 @@ subprojects {
// Ignore core since its a scala project
if (it.path != ':core') {
+ apply plugin: "jacoco"
jacoco {
- toolVersion = "0.8.0"
+ toolVersion = "0.8.1"
}
// NOTE: Jacoco Gradle plugin does not support "offline instrumentation" this means that classes mocked by PowerMock
@@ -518,7 +508,7 @@ for ( sv in availableScalaVersions ) {
}
def connectPkgs = ['connect:api', 'connect:runtime', 'connect:transforms', 'connect:json', 'connect:file']
-def pkgs = ['clients', 'examples', 'log4j-appender', 'tools', 'streams', 'streams:test-utils', 'streams:examples'] + connectPkgs
+def pkgs = ['clients', 'examples', 'log4j-appender', 'tools', 'streams', 'streams:streams-scala', 'streams:test-utils', 'streams:examples'] + connectPkgs
/** Create one task per default Scala version */
def withDefScalaVersions(taskName) {
@@ -591,8 +581,7 @@ project(':core') {
scoverage libs.scoveragePlugin
scoverage libs.scoverageRuntime
}
-
- jacocoTestReport.enabled = false
+
scoverage {
reportDir = file("${rootProject.buildDir}/scoverage")
highlighting = false
@@ -634,6 +623,13 @@ project(':core') {
standardOutput = new File(generatedDocsDir, "protocol_errors.html").newOutputStream()
}
+ task genProtocolTypesDocs(type: JavaExec) {
+ classpath = sourceSets.main.runtimeClasspath
+ main = 'org.apache.kafka.common.protocol.types.Type'
+ if( !generatedDocsDir.exists() ) { generatedDocsDir.mkdirs() }
+ standardOutput = new File(generatedDocsDir, "protocol_types.html").newOutputStream()
+ }
+
task genProtocolApiKeyDocs(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = 'org.apache.kafka.common.protocol.ApiKeys'
@@ -697,7 +693,7 @@ project(':core') {
standardOutput = new File(generatedDocsDir, "producer_metrics.html").newOutputStream()
}
- task siteDocsTar(dependsOn: ['genProtocolErrorDocs', 'genProtocolApiKeyDocs', 'genProtocolMessageDocs',
+ task siteDocsTar(dependsOn: ['genProtocolErrorDocs', 'genProtocolTypesDocs', 'genProtocolApiKeyDocs', 'genProtocolMessageDocs',
'genAdminClientConfigDocs', 'genProducerConfigDocs', 'genConsumerConfigDocs',
'genKafkaConfigDocs', 'genTopicConfigDocs',
':connect:runtime:genConnectConfigDocs', ':connect:runtime:genConnectTransformationDocs',
@@ -734,6 +730,8 @@ project(':core') {
from(project(':connect:file').configurations.runtime) { into("libs/") }
from(project(':streams').jar) { into("libs/") }
from(project(':streams').configurations.runtime) { into("libs/") }
+ from(project(':streams:streams-scala').jar) { into("libs/") }
+ from(project(':streams:streams-scala').configurations.runtime) { into("libs/") }
from(project(':streams:test-utils').jar) { into("libs/") }
from(project(':streams:test-utils').configurations.runtime) { into("libs/") }
from(project(':streams:examples').jar) { into("libs/") }
@@ -789,6 +787,7 @@ project(':clients') {
compile libs.lz4
compile libs.snappy
compile libs.slf4jApi
+ compile libs.jacksonDatabind // for SASL/OAUTHBEARER bearer token parsing
testCompile libs.bcpkix
testCompile libs.junit
@@ -859,6 +858,8 @@ project(':clients') {
include "**/org/apache/kafka/common/config/*"
include "**/org/apache/kafka/common/security/auth/*"
include "**/org/apache/kafka/server/policy/*"
+ include "**/org/apache/kafka/common/security/token/delegation/*"
+ include "**/org/apache/kafka/common/security/oauthbearer/*"
}
}
@@ -874,6 +875,9 @@ project(':tools') {
compile libs.jacksonJaxrsJsonProvider
compile libs.jerseyContainerServlet
+ compile libs.jerseyHk2
+ compile libs.jaxbApi // Jersey dependency that was available in the JDK before Java 9
+ compile libs.activation // Jersey dependency that was available in the JDK before Java 9
compile libs.jettyServer
compile libs.jettyServlet
compile libs.jettyServlets
@@ -918,13 +922,17 @@ project(':streams') {
compile libs.slf4jApi
compile libs.rocksDBJni
+ // testCompileOnly prevents streams from exporting a dependency on test-utils, which would cause a dependency cycle
+ testCompileOnly project(':streams:test-utils')
testCompile project(':clients').sourceSets.test.output
testCompile project(':core')
testCompile project(':core').sourceSets.test.output
+ testCompile libs.log4j
testCompile libs.junit
testCompile libs.easymock
testCompile libs.bcpkix
+ testRuntimeOnly project(':streams:test-utils')
testRuntime libs.slf4jlog4j
}
@@ -961,6 +969,45 @@ project(':streams') {
}
}
+project(':streams:streams-scala') {
+ println "Building project 'streams-scala' with Scala version ${versions.scala}"
+ apply plugin: 'scala'
+ archivesBaseName = "kafka-streams-scala_${versions.baseScala}"
+
+ dependencies {
+ compile project(':streams')
+
+ compile libs.scalaLibrary
+
+ testCompile project(':core')
+ testCompile project(':core').sourceSets.test.output
+ testCompile project(':streams').sourceSets.test.output
+ testCompile project(':clients').sourceSets.test.output
+
+ testCompile libs.junit
+ testCompile libs.scalatest
+
+ testRuntime libs.slf4jlog4j
+ }
+
+ javadoc {
+ include "**/org/apache/kafka/streams/scala/**"
+ }
+
+ tasks.create(name: "copyDependantLibs", type: Copy) {
+ from (configurations.runtime) {
+ exclude('kafka-streams*')
+ }
+ into "$buildDir/dependant-libs-${versions.scala}"
+ duplicatesStrategy 'exclude'
+ }
+
+ jar {
+ dependsOn 'copyDependantLibs'
+ }
+
+}
+
project(':streams:test-utils') {
archivesBaseName = "kafka-streams-test-utils"
@@ -1000,6 +1047,10 @@ project(':streams:examples') {
compile project(':streams')
compile project(':connect:json') // this dependency should be removed after we unify data API
compile libs.slf4jlog4j
+
+ testCompile project(':streams:test-utils')
+ testCompile project(':clients').sourceSets.test.output // for org.apache.kafka.test.IntegrationTest
+ testCompile libs.junit
}
javadoc {
@@ -1019,6 +1070,78 @@ project(':streams:examples') {
}
}
+project(':streams:upgrade-system-tests-0100') {
+ archivesBaseName = "kafka-streams-upgrade-system-tests-0100"
+
+ dependencies {
+ testCompile libs.kafkaStreams_0100
+ }
+
+ systemTestLibs {
+ dependsOn testJar
+ }
+}
+
+project(':streams:upgrade-system-tests-0101') {
+ archivesBaseName = "kafka-streams-upgrade-system-tests-0101"
+
+ dependencies {
+ testCompile libs.kafkaStreams_0101
+ }
+
+ systemTestLibs {
+ dependsOn testJar
+ }
+}
+
+project(':streams:upgrade-system-tests-0102') {
+ archivesBaseName = "kafka-streams-upgrade-system-tests-0102"
+
+ dependencies {
+ testCompile libs.kafkaStreams_0102
+ }
+
+ systemTestLibs {
+ dependsOn testJar
+ }
+}
+
+project(':streams:upgrade-system-tests-0110') {
+ archivesBaseName = "kafka-streams-upgrade-system-tests-0110"
+
+ dependencies {
+ testCompile libs.kafkaStreams_0110
+ }
+
+ systemTestLibs {
+ dependsOn testJar
+ }
+}
+
+project(':streams:upgrade-system-tests-10') {
+ archivesBaseName = "kafka-streams-upgrade-system-tests-10"
+
+ dependencies {
+ testCompile libs.kafkaStreams_10
+ }
+
+ systemTestLibs {
+ dependsOn testJar
+ }
+}
+
+project(':streams:upgrade-system-tests-11') {
+ archivesBaseName = "kafka-streams-upgrade-system-tests-11"
+
+ dependencies {
+ testCompile libs.kafkaStreams_11
+ }
+
+ systemTestLibs {
+ dependsOn testJar
+ }
+}
+
project(':jmh-benchmarks') {
apply plugin: 'com.github.johnrengelman.shadow'
@@ -1084,6 +1207,7 @@ project(':connect:api') {
dependencies {
compile project(':clients')
compile libs.slf4jApi
+ compile libs.jerseyContainerServlet
testCompile libs.junit
@@ -1195,14 +1319,19 @@ project(':connect:runtime') {
archivesBaseName = "connect-runtime"
dependencies {
+
compile project(':connect:api')
- compile project(":connect:transforms")
compile project(':clients')
compile project(':tools')
- compile libs.slf4jApi
+ compile project(':connect:json')
+ compile project(':connect:transforms')
+ compile libs.slf4jApi
compile libs.jacksonJaxrsJsonProvider
compile libs.jerseyContainerServlet
+ compile libs.jerseyHk2
+ compile libs.jaxbApi // Jersey dependency that was available in the JDK before Java 9
+ compile libs.activation // Jersey dependency that was available in the JDK before Java 9
compile libs.jettyServer
compile libs.jettyServlet
compile libs.jettyServlets
@@ -1216,7 +1345,6 @@ project(':connect:runtime') {
testCompile libs.powermockJunit4
testCompile libs.powermockEasymock
- testCompile project(":connect:json")
testCompile project(':clients').sourceSets.test.output
testRuntime libs.slf4jlog4j
@@ -1304,6 +1432,45 @@ project(':connect:file') {
}
}
+project(':connect:basic-auth-extension') {
+ archivesBaseName = "connect-basic-auth-extension"
+
+ dependencies {
+ compile project(':connect:api')
+ compile libs.slf4jApi
+
+ testCompile libs.bcpkix
+ testCompile libs.easymock
+ testCompile libs.junit
+ testCompile libs.powermockJunit4
+ testCompile libs.powermockEasymock
+ testCompile project(':clients').sourceSets.test.output
+
+ testRuntime libs.slf4jlog4j
+ }
+
+ javadoc {
+ enabled = false
+ }
+
+ tasks.create(name: "copyDependantLibs", type: Copy) {
+ from (configurations.testRuntime) {
+ include('slf4j-log4j12*')
+ include('log4j*jar')
+ }
+ from (configurations.runtime) {
+ exclude('kafka-clients*')
+ exclude('connect-*')
+ }
+ into "$buildDir/dependant-libs"
+ duplicatesStrategy 'exclude'
+ }
+
+ jar {
+ dependsOn copyDependantLibs
+ }
+}
+
task aggregatedJavadoc(type: Javadoc) {
def projectsWithJavadoc = subprojects.findAll { it.javadoc.enabled }
source = projectsWithJavadoc.collect { it.sourceSets.main.allJava }
diff --git a/checkstyle/checkstyle.xml b/checkstyle/checkstyle.xml
index ccab85ce4fecb..ad8545027a000 100644
--- a/checkstyle/checkstyle.xml
+++ b/checkstyle/checkstyle.xml
@@ -105,7 +105,7 @@
-
+
@@ -114,7 +114,7 @@
-
+
diff --git a/checkstyle/import-control.xml b/checkstyle/import-control.xml
index 000acc38463b4..5549205bd1312 100644
--- a/checkstyle/import-control.xml
+++ b/checkstyle/import-control.xml
@@ -101,6 +101,9 @@
+
+
+
@@ -164,6 +167,8 @@
+
+
@@ -205,6 +210,8 @@
+
+
@@ -247,6 +254,10 @@
+
+
+
+
@@ -279,6 +290,7 @@
+
@@ -296,7 +308,16 @@
-
+
+
+
+
+
+
+
+
+
+
@@ -316,6 +337,8 @@
+
+
diff --git a/checkstyle/suppressions.xml b/checkstyle/suppressions.xml
index 45ee4e60a7c07..5bf69b6b65f9b 100644
--- a/checkstyle/suppressions.xml
+++ b/checkstyle/suppressions.xml
@@ -10,7 +10,7 @@
+ files="(Fetcher|Sender|SenderTest|ConsumerCoordinator|KafkaConsumer|KafkaProducer|Utils|TransactionManagerTest|KafkaAdminClient|NetworkClient|AdminClient).java"/>
+ files="(Errors|SaslAuthenticatorTest|AgentTest|CoordinatorTest).java"/>
@@ -57,7 +57,7 @@
files="AbstractRequest.java|KerberosLogin.java|WorkerSinkTaskTest.java|TransactionManagerTest.java"/>
+ files="(BufferPool|Fetcher|MetricName|Node|ConfigDef|RecordBatch|SslFactory|SslTransportLayer|MetadataResponse|KerberosLogin|Selector|Sender|Serdes|TokenInformation|Agent|Values|PluginUtils|MiniTrogdorCluster).java"/>
+
+
@@ -80,7 +83,7 @@
files="(KafkaConfigBackingStore|RequestResponseTest|WorkerSinkTaskTest).java"/>
+ files="(WorkerSinkTask|WorkerSourceTask).java"/>
-
-
-
-
-
+ files="(AbstractStatus|ConnectHeaders|ConnectRecord|ConnectSchema|DistributedHerder|FileStreamSourceTask|JsonConverter|KafkaConfigBackingStore).java"/>
+
@@ -155,11 +149,7 @@
files="StreamsPartitionAssignor.java"/>
-
-
+ files="(ProcessorStateManager|InternalTopologyBuilder|StreamsPartitionAssignor|StreamThread).java"/>
+ files="EosTestDriver|KStreamKStreamJoinTest.java|SmokeTestDriver.java"/>
@@ -212,6 +202,8 @@
files="SignalLogger.java"/>
+ now) {
+ return state.throttleUntilTimeMs - now;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Return the number of milliseconds to wait, based on the connection state and the throttle time, before
+ * attempting to send data. If the connection has been established but being throttled, return throttle delay.
+ * Otherwise, return connection delay.
+ * @param id the connection to check
+ * @param now the current time in ms
+ */
+ public long pollDelayMs(String id, long now) {
+ long throttleDelayMs = throttleDelayMs(id, now);
+ if (isConnected(id) && throttleDelayMs > 0) {
+ return throttleDelayMs;
+ } else {
+ return connectionDelay(id, now);
+ }
+ }
+
/**
* Enter the checking_api_versions state for the given node.
* @param id the connection identifier
@@ -144,6 +187,7 @@ public void checkingApiVersions(String id) {
public void ready(String id) {
NodeConnectionState nodeState = nodeState(id);
nodeState.state = ConnectionState.READY;
+ nodeState.authenticationException = null;
resetReconnectBackoff(nodeState);
}
@@ -162,26 +206,43 @@ public void authenticationFailed(String id, long now, AuthenticationException ex
}
/**
- * Return true if the connection is ready.
+ * Return true if the connection is in the READY state and currently not throttled.
+ *
* @param id the connection identifier
+ * @param now the current time
*/
- public boolean isReady(String id) {
- NodeConnectionState state = nodeState.get(id);
- return state != null && state.state == ConnectionState.READY;
+ public boolean isReady(String id, long now) {
+ return isReady(nodeState.get(id), now);
+ }
+
+ private boolean isReady(NodeConnectionState state, long now) {
+ return state != null && state.state == ConnectionState.READY && state.throttleUntilTimeMs <= now;
}
/**
- * Return true if there is at least one node with connection in ready state and false otherwise.
+ * Return true if there is at least one node with connection in the READY state and not throttled. Returns false
+ * otherwise.
+ *
+ * @param now the current time
*/
- public boolean hasReadyNodes() {
+ public boolean hasReadyNodes(long now) {
for (Map.Entry entry : nodeState.entrySet()) {
- NodeConnectionState state = entry.getValue();
- if (state != null && state.state == ConnectionState.READY)
+ if (isReady(entry.getValue(), now)) {
return true;
+ }
}
return false;
}
+ /**
+ * Return true if the connection has been established
+ * @param id The id of the node to check
+ */
+ public boolean isConnected(String id) {
+ NodeConnectionState state = nodeState.get(id);
+ return state != null && state.state.isConnected();
+ }
+
/**
* Return true if the connection has been disconnected
* @param id The id of the node to check
@@ -271,6 +332,8 @@ private static class NodeConnectionState {
long lastConnectAttemptMs;
long failedAttempts;
long reconnectBackoffMs;
+ // Connection is being throttled if current time < throttleUntilTimeMs.
+ long throttleUntilTimeMs;
public NodeConnectionState(ConnectionState state, long lastConnectAttempt, long reconnectBackoffMs) {
this.state = state;
@@ -278,10 +341,11 @@ public NodeConnectionState(ConnectionState state, long lastConnectAttempt, long
this.lastConnectAttemptMs = lastConnectAttempt;
this.failedAttempts = 0;
this.reconnectBackoffMs = reconnectBackoffMs;
+ this.throttleUntilTimeMs = 0;
}
public String toString() {
- return "NodeState(" + state + ", " + lastConnectAttemptMs + ", " + failedAttempts + ")";
+ return "NodeState(" + state + ", " + lastConnectAttemptMs + ", " + failedAttempts + ", " + throttleUntilTimeMs + ")";
}
}
}
diff --git a/clients/src/main/java/org/apache/kafka/clients/ConnectionState.java b/clients/src/main/java/org/apache/kafka/clients/ConnectionState.java
index 28b43d67fb1d7..f92c7fa037b0e 100644
--- a/clients/src/main/java/org/apache/kafka/clients/ConnectionState.java
+++ b/clients/src/main/java/org/apache/kafka/clients/ConnectionState.java
@@ -31,4 +31,8 @@ public enum ConnectionState {
public boolean isDisconnected() {
return this == AUTHENTICATION_FAILED || this == DISCONNECTED;
}
+
+ public boolean isConnected() {
+ return this == CHECKING_API_VERSIONS || this == READY;
+ }
}
diff --git a/clients/src/main/java/org/apache/kafka/clients/InFlightRequests.java b/clients/src/main/java/org/apache/kafka/clients/InFlightRequests.java
index a062818e3b2c4..5caee2d4c87d8 100644
--- a/clients/src/main/java/org/apache/kafka/clients/InFlightRequests.java
+++ b/clients/src/main/java/org/apache/kafka/clients/InFlightRequests.java
@@ -20,12 +20,12 @@
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
-
/**
* The set of requests which have been sent or are being sent but haven't yet received a response
*/
@@ -151,9 +151,14 @@ public Iterable clearAll(String node) {
if (reqs == null) {
return Collections.emptyList();
} else {
- Deque clearedRequests = requests.remove(node);
+ final Deque clearedRequests = requests.remove(node);
inFlightRequestCount.getAndAdd(-clearedRequests.size());
- return clearedRequests;
+ return new Iterable() {
+ @Override
+ public Iterator iterator() {
+ return clearedRequests.descendingIterator();
+ }
+ };
}
}
diff --git a/clients/src/main/java/org/apache/kafka/clients/KafkaClient.java b/clients/src/main/java/org/apache/kafka/clients/KafkaClient.java
index 0a9b51981e7ff..49bf3a3eab97b 100644
--- a/clients/src/main/java/org/apache/kafka/clients/KafkaClient.java
+++ b/clients/src/main/java/org/apache/kafka/clients/KafkaClient.java
@@ -48,7 +48,7 @@ public interface KafkaClient extends Closeable {
boolean ready(Node node, long now);
/**
- * Returns the number of milliseconds to wait, based on the connection state, before attempting to send data. When
+ * Return the number of milliseconds to wait, based on the connection state, before attempting to send data. When
* disconnected, this respects the reconnect backoff time. When connecting or connected, this handles slow/stalled
* connections.
*
@@ -58,6 +58,16 @@ public interface KafkaClient extends Closeable {
*/
long connectionDelay(Node node, long now);
+ /**
+ * Return the number of milliseconds to wait, based on the connection state and the throttle time, before
+ * attempting to send data. If the connection has been established but being throttled, return throttle delay.
+ * Otherwise, return connection delay.
+ *
+ * @param node the connection to check
+ * @param now the current time in ms
+ */
+ long pollDelayMs(Node node, long now);
+
/**
* Check if the connection of the node has failed, based on the connection state. Such connection failure are
* usually transient and can be resumed in the next {@link #ready(org.apache.kafka.common.Node, long)} }
@@ -145,9 +155,12 @@ public interface KafkaClient extends Closeable {
boolean hasInFlightRequests(String nodeId);
/**
- * Return true if there is at least one node with connection in ready state and false otherwise.
+ * Return true if there is at least one node with connection in the READY state and not throttled. Returns false
+ * otherwise.
+ *
+ * @param now the current time
*/
- boolean hasReadyNodes();
+ boolean hasReadyNodes(long now);
/**
* Wake up the client if it is currently blocked waiting for I/O
diff --git a/clients/src/main/java/org/apache/kafka/clients/MetadataUpdater.java b/clients/src/main/java/org/apache/kafka/clients/MetadataUpdater.java
index 126728342d4c7..09ed995d14c3e 100644
--- a/clients/src/main/java/org/apache/kafka/clients/MetadataUpdater.java
+++ b/clients/src/main/java/org/apache/kafka/clients/MetadataUpdater.java
@@ -29,7 +29,7 @@
*
* This class is not thread-safe!
*/
-interface MetadataUpdater {
+public interface MetadataUpdater {
/**
* Gets the current cluster info without blocking.
diff --git a/clients/src/main/java/org/apache/kafka/clients/NetworkClient.java b/clients/src/main/java/org/apache/kafka/clients/NetworkClient.java
index 4b291b5eb67a4..26fa467f81a50 100644
--- a/clients/src/main/java/org/apache/kafka/clients/NetworkClient.java
+++ b/clients/src/main/java/org/apache/kafka/clients/NetworkClient.java
@@ -300,6 +300,22 @@ public long connectionDelay(Node node, long now) {
return connectionStates.connectionDelay(node.idString(), now);
}
+ // Return the remaining throttling delay in milliseconds if throttling is in progress. Return 0, otherwise.
+ // This is for testing.
+ public long throttleDelayMs(Node node, long now) {
+ return connectionStates.throttleDelayMs(node.idString(), now);
+ }
+
+ /**
+ * Return the poll delay in milliseconds based on both connection and throttle delay.
+ * @param node the connection to check
+ * @param now the current time in ms
+ */
+ @Override
+ public long pollDelayMs(Node node, long now) {
+ return connectionStates.pollDelayMs(node.idString(), now);
+ }
+
/**
* Check if the connection of the node has failed, based on the connection state. Such connection failure are
* usually transient and can be resumed in the next {@link #ready(org.apache.kafka.common.Node, long)} }
@@ -336,16 +352,18 @@ public AuthenticationException authenticationException(Node node) {
public boolean isReady(Node node, long now) {
// if we need to update our metadata now declare all requests unready to make metadata requests first
// priority
- return !metadataUpdater.isUpdateDue(now) && canSendRequest(node.idString());
+ return !metadataUpdater.isUpdateDue(now) && canSendRequest(node.idString(), now);
}
/**
* Are we connected and ready and able to send more requests to the given connection?
*
* @param node The node
+ * @param now the current timestamp
*/
- private boolean canSendRequest(String node) {
- return connectionStates.isReady(node) && selector.isChannelReady(node) && inFlightRequests.canSendMore(node);
+ private boolean canSendRequest(String node, long now) {
+ return connectionStates.isReady(node, now) && selector.isChannelReady(node) &&
+ inFlightRequests.canSendMore(node);
}
/**
@@ -373,7 +391,7 @@ private void doSend(ClientRequest clientRequest, boolean isInternalRequest, long
// will be slightly different for some internal requests (for
// example, ApiVersionsRequests can be sent prior to being in
// READY state.)
- if (!canSendRequest(nodeId))
+ if (!canSendRequest(nodeId, now))
throw new IllegalStateException("Attempt to send a request to node " + nodeId + " which is not ready.");
}
AbstractRequest.Builder> builder = clientRequest.requestBuilder();
@@ -513,8 +531,8 @@ public boolean hasInFlightRequests(String node) {
}
@Override
- public boolean hasReadyNodes() {
- return connectionStates.hasReadyNodes();
+ public boolean hasReadyNodes(long now) {
+ return connectionStates.hasReadyNodes(now);
}
/**
@@ -603,11 +621,13 @@ private void processDisconnection(List responses, String nodeId,
nodesNeedingApiVersionsFetch.remove(nodeId);
switch (disconnectState.state()) {
case AUTHENTICATION_FAILED:
- connectionStates.authenticationFailed(nodeId, now, disconnectState.exception());
- log.error("Connection to node {} failed authentication due to: {}", nodeId, disconnectState.exception().getMessage());
+ AuthenticationException exception = disconnectState.exception();
+ connectionStates.authenticationFailed(nodeId, now, exception);
+ metadataUpdater.handleAuthenticationFailure(exception);
+ log.error("Connection to node {} failed authentication due to: {}", nodeId, exception.getMessage());
break;
case AUTHENTICATE:
- // This warning applies to older brokers which dont provide feedback on authentication failures
+ // This warning applies to older brokers which don't provide feedback on authentication failures
log.warn("Connection to node {} terminated during authentication. This may indicate " +
"that authentication failed due to invalid credentials.", nodeId);
break;
@@ -625,9 +645,6 @@ private void processDisconnection(List responses, String nodeId,
else if (request.header.apiKey() == ApiKeys.METADATA)
metadataUpdater.handleDisconnection(request.destination);
}
- AuthenticationException authenticationException = connectionStates.authenticationException(nodeId);
- if (authenticationException != null)
- metadataUpdater.handleAuthenticationFailure(authenticationException);
}
/**
@@ -673,6 +690,24 @@ private void handleCompletedSends(List responses, long now) {
}
}
+ /**
+ * If a response from a node includes a non-zero throttle delay and client-side throttling has been enabled for
+ * the connection to the node, throttle the connection for the specified delay.
+ *
+ * @param response the response
+ * @param apiVersion the API version of the response
+ * @param nodeId the id of the node
+ * @param now The current time
+ */
+ private void maybeThrottle(AbstractResponse response, short apiVersion, String nodeId, long now) {
+ int throttleTimeMs = response.throttleTimeMs();
+ if (throttleTimeMs > 0 && response.shouldClientThrottle(apiVersion)) {
+ connectionStates.throttle(nodeId, now + throttleTimeMs);
+ log.trace("Connection to node {} is throttled for {} ms until timestamp {}", nodeId, throttleTimeMs,
+ now + throttleTimeMs);
+ }
+ }
+
/**
* Handle any completed receives and update the response list with the responses received.
*
@@ -689,7 +724,9 @@ private void handleCompletedReceives(List responses, long now) {
log.trace("Completed receive from node {} for {} with correlation id {}, received {}", req.destination,
req.header.apiKey(), req.header.correlationId(), responseStruct);
}
+ // If the received response includes a throttle delay, throttle the connection.
AbstractResponse body = AbstractResponse.parseResponse(req.header.apiKey(), responseStruct);
+ maybeThrottle(body, req.header.apiVersion(), req.destination, now);
if (req.isInternalRequest && body instanceof MetadataResponse)
metadataUpdater.handleCompletedMetadataResponse(req.header, now, (MetadataResponse) body);
else if (req.isInternalRequest && body instanceof ApiVersionsResponse)
@@ -716,9 +753,7 @@ private void handleApiVersionsResponse(List responses,
NodeApiVersions nodeVersionInfo = new NodeApiVersions(apiVersionsResponse.apiVersions());
apiVersions.update(node, nodeVersionInfo);
this.connectionStates.ready(node);
- if (log.isDebugEnabled()) {
- log.debug("Recorded API versions for node {}: {}", node, nodeVersionInfo);
- }
+ log.debug("Recorded API versions for node {}: {}", node, nodeVersionInfo);
}
/**
@@ -833,6 +868,7 @@ public long maybeUpdate(long now) {
long waitForMetadataFetch = this.metadataFetchInProgress ? requestTimeoutMs : 0;
long metadataTimeout = Math.max(timeToNextMetadataUpdate, waitForMetadataFetch);
+
if (metadataTimeout > 0) {
return metadataTimeout;
}
@@ -914,7 +950,7 @@ private boolean isAnyNodeConnecting() {
private long maybeUpdate(long now, Node node) {
String nodeConnectionId = node.idString();
- if (canSendRequest(nodeConnectionId)) {
+ if (canSendRequest(nodeConnectionId, now)) {
this.metadataFetchInProgress = true;
MetadataRequest.Builder metadataRequest;
if (metadata.needMetadataForAllTopics())
diff --git a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/StaleMetadataException.java b/clients/src/main/java/org/apache/kafka/clients/StaleMetadataException.java
similarity index 74%
rename from clients/src/main/java/org/apache/kafka/clients/consumer/internals/StaleMetadataException.java
rename to clients/src/main/java/org/apache/kafka/clients/StaleMetadataException.java
index 53110d3838747..dafc2d5483c4c 100644
--- a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/StaleMetadataException.java
+++ b/clients/src/main/java/org/apache/kafka/clients/StaleMetadataException.java
@@ -14,13 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.kafka.clients.consumer.internals;
+package org.apache.kafka.clients;
import org.apache.kafka.common.errors.InvalidMetadataException;
/**
- * Thrown when metadata is old and needs to be refreshed.
+ * Thrown when current metadata cannot be used. This is often used as a way to trigger a metadata
+ * update before retrying another operation.
+ *
+ * Note: this is not a public API.
*/
public class StaleMetadataException extends InvalidMetadataException {
private static final long serialVersionUID = 1L;
+
+ public StaleMetadataException() {}
+
+ public StaleMetadataException(String message) {
+ super(message);
+ }
}
diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/AdminClient.java b/clients/src/main/java/org/apache/kafka/clients/admin/AdminClient.java
index 897e127d5577b..0171b617e7ca2 100644
--- a/clients/src/main/java/org/apache/kafka/clients/admin/AdminClient.java
+++ b/clients/src/main/java/org/apache/kafka/clients/admin/AdminClient.java
@@ -535,4 +535,237 @@ public DeleteRecordsResult deleteRecords(Map re
*/
public abstract DeleteRecordsResult deleteRecords(Map recordsToDelete,
DeleteRecordsOptions options);
+
+ /**
+ *
Create a Delegation Token.
+ *
+ *
This is a convenience method for {@link #createDelegationToken(CreateDelegationTokenOptions)} with default options.
+ * See the overload for more details.
This operation is supported by brokers with version 1.1.0 or higher.
+ *
+ *
The following exceptions can be anticipated when calling {@code get()} on the futures obtained from the
+ * {@link CreateDelegationTokenResult#delegationToken() delegationToken()} method of the returned {@code CreateDelegationTokenResult}
+ *
+ *
{@link org.apache.kafka.common.errors.UnsupportedByAuthenticationException}
+ * If the request sent on PLAINTEXT/1-way SSL channels or delegation token authenticated channels.
+ *
{@link org.apache.kafka.common.errors.InvalidPrincipalTypeException}
+ * if the renewers principal type is not supported.
+ *
{@link org.apache.kafka.common.errors.DelegationTokenDisabledException}
+ * if the delegation token feature is disabled.
+ *
{@link org.apache.kafka.common.errors.TimeoutException}
+ * if the request was not completed in within the given {@link CreateDelegationTokenOptions#timeoutMs()}.
+ *
+ *
+ * @param options The options to use when creating delegation token.
+ * @return The DeleteRecordsResult.
+ */
+ public abstract CreateDelegationTokenResult createDelegationToken(CreateDelegationTokenOptions options);
+
+
+ /**
+ *
Renew a Delegation Token.
+ *
+ *
This is a convenience method for {@link #renewDelegationToken(byte[], RenewDelegationTokenOptions)} with default options.
+ * See the overload for more details.
+ *
+ *
+ * @param hmac HMAC of the Delegation token
+ * @return The RenewDelegationTokenResult.
+ */
+ public RenewDelegationTokenResult renewDelegationToken(byte[] hmac) {
+ return renewDelegationToken(hmac, new RenewDelegationTokenOptions());
+ }
+
+ /**
+ *
Renew a Delegation Token.
+ *
+ *
This operation is supported by brokers with version 1.1.0 or higher.
+ *
+ *
The following exceptions can be anticipated when calling {@code get()} on the futures obtained from the
+ * {@link RenewDelegationTokenResult#expiryTimestamp() expiryTimestamp()} method of the returned {@code RenewDelegationTokenResult}
+ *
+ *
{@link org.apache.kafka.common.errors.UnsupportedByAuthenticationException}
+ * If the request sent on PLAINTEXT/1-way SSL channels or delegation token authenticated channels.
+ *
{@link org.apache.kafka.common.errors.DelegationTokenDisabledException}
+ * if the delegation token feature is disabled.
+ *
{@link org.apache.kafka.common.errors.DelegationTokenNotFoundException}
+ * if the delegation token is not found on server.
+ *
{@link org.apache.kafka.common.errors.DelegationTokenOwnerMismatchException}
+ * if the authenticated user is not owner/renewer of the token.
+ *
{@link org.apache.kafka.common.errors.DelegationTokenExpiredException}
+ * if the delegation token is expired.
+ *
{@link org.apache.kafka.common.errors.TimeoutException}
+ * if the request was not completed in within the given {@link RenewDelegationTokenOptions#timeoutMs()}.
+ *
+ *
+ * @param hmac HMAC of the Delegation token
+ * @param options The options to use when renewing delegation token.
+ * @return The RenewDelegationTokenResult.
+ */
+ public abstract RenewDelegationTokenResult renewDelegationToken(byte[] hmac, RenewDelegationTokenOptions options);
+
+ /**
+ *
Expire a Delegation Token.
+ *
+ *
This is a convenience method for {@link #expireDelegationToken(byte[], ExpireDelegationTokenOptions)} with default options.
+ * This will expire the token immediately. See the overload for more details.
+ *
+ * @param hmac HMAC of the Delegation token
+ * @return The ExpireDelegationTokenResult.
+ */
+ public ExpireDelegationTokenResult expireDelegationToken(byte[] hmac) {
+ return expireDelegationToken(hmac, new ExpireDelegationTokenOptions());
+ }
+
+ /**
+ *
Expire a Delegation Token.
+ *
+ *
This operation is supported by brokers with version 1.1.0 or higher.
+ *
+ *
The following exceptions can be anticipated when calling {@code get()} on the futures obtained from the
+ * {@link ExpireDelegationTokenResult#expiryTimestamp() expiryTimestamp()} method of the returned {@code ExpireDelegationTokenResult}
+ *
+ *
{@link org.apache.kafka.common.errors.UnsupportedByAuthenticationException}
+ * If the request sent on PLAINTEXT/1-way SSL channels or delegation token authenticated channels.
+ *
{@link org.apache.kafka.common.errors.DelegationTokenDisabledException}
+ * if the delegation token feature is disabled.
+ *
{@link org.apache.kafka.common.errors.DelegationTokenNotFoundException}
+ * if the delegation token is not found on server.
+ *
{@link org.apache.kafka.common.errors.DelegationTokenOwnerMismatchException}
+ * if the authenticated user is not owner/renewer of the requested token.
+ *
{@link org.apache.kafka.common.errors.DelegationTokenExpiredException}
+ * if the delegation token is expired.
+ *
{@link org.apache.kafka.common.errors.TimeoutException}
+ * if the request was not completed in within the given {@link ExpireDelegationTokenOptions#timeoutMs()}.
+ *
+ *
+ * @param hmac HMAC of the Delegation token
+ * @param options The options to use when expiring delegation token.
+ * @return The ExpireDelegationTokenResult.
+ */
+ public abstract ExpireDelegationTokenResult expireDelegationToken(byte[] hmac, ExpireDelegationTokenOptions options);
+
+ /**
+ *
Describe the Delegation Tokens.
+ *
+ *
This is a convenience method for {@link #describeDelegationToken(DescribeDelegationTokenOptions)} with default options.
+ * This will return all the user owned tokens and tokens where user have Describe permission. See the overload for more details.
This operation is supported by brokers with version 1.1.0 or higher.
+ *
+ *
The following exceptions can be anticipated when calling {@code get()} on the futures obtained from the
+ * {@link DescribeDelegationTokenResult#delegationTokens() delegationTokens()} method of the returned {@code DescribeDelegationTokenResult}
+ *
+ *
{@link org.apache.kafka.common.errors.UnsupportedByAuthenticationException}
+ * If the request sent on PLAINTEXT/1-way SSL channels or delegation token authenticated channels.
+ *
{@link org.apache.kafka.common.errors.DelegationTokenDisabledException}
+ * if the delegation token feature is disabled.
+ *
{@link org.apache.kafka.common.errors.TimeoutException}
+ * if the request was not completed in within the given {@link DescribeDelegationTokenOptions#timeoutMs()}.
+ *
+ *
+ * @param options The options to use when describing delegation tokens.
+ * @return The DescribeDelegationTokenResult.
+ */
+ public abstract DescribeDelegationTokenResult describeDelegationToken(DescribeDelegationTokenOptions options);
+
+ /**
+ * Describe some group IDs in the cluster.
+ *
+ * @param groupIds The IDs of the groups to describe.
+ * @param options The options to use when describing the groups.
+ * @return The DescribeConsumerGroupResult.
+ */
+ public abstract DescribeConsumerGroupsResult describeConsumerGroups(Collection groupIds,
+ DescribeConsumerGroupsOptions options);
+
+ /**
+ * Describe some group IDs in the cluster, with the default options.
+ *
+ * This is a convenience method for
+ * #{@link AdminClient#describeConsumerGroups(Collection, DescribeConsumerGroupsOptions)} with
+ * default options. See the overload for more details.
+ *
+ * @param groupIds The IDs of the groups to describe.
+ * @return The DescribeConsumerGroupResult.
+ */
+ public DescribeConsumerGroupsResult describeConsumerGroups(Collection groupIds) {
+ return describeConsumerGroups(groupIds, new DescribeConsumerGroupsOptions());
+ }
+
+ /**
+ * List the consumer groups available in the cluster.
+ *
+ * @param options The options to use when listing the consumer groups.
+ * @return The ListGroupsResult.
+ */
+ public abstract ListConsumerGroupsResult listConsumerGroups(ListConsumerGroupsOptions options);
+
+ /**
+ * List the consumer groups available in the cluster with the default options.
+ *
+ * This is a convenience method for #{@link AdminClient#listConsumerGroups(ListConsumerGroupsOptions)} with default options.
+ * See the overload for more details.
+ *
+ * @return The ListGroupsResult.
+ */
+ public ListConsumerGroupsResult listConsumerGroups() {
+ return listConsumerGroups(new ListConsumerGroupsOptions());
+ }
+
+ /**
+ * List the consumer group offsets available in the cluster.
+ *
+ * @param options The options to use when listing the consumer group offsets.
+ * @return The ListGroupOffsetsResult
+ */
+ public abstract ListConsumerGroupOffsetsResult listConsumerGroupOffsets(String groupId, ListConsumerGroupOffsetsOptions options);
+
+ /**
+ * List the consumer group offsets available in the cluster with the default options.
+ *
+ * This is a convenience method for #{@link AdminClient#listConsumerGroupOffsets(String, ListConsumerGroupOffsetsOptions)} with default options.
+ *
+ * @return The ListGroupOffsetsResult.
+ */
+ public ListConsumerGroupOffsetsResult listConsumerGroupOffsets(String groupId) {
+ return listConsumerGroupOffsets(groupId, new ListConsumerGroupOffsetsOptions());
+ }
+
+ /**
+ * Delete consumer groups from the cluster.
+ *
+ * @param options The options to use when deleting a consumer group.
+ * @return The DeletConsumerGroupResult.
+ */
+ public abstract DeleteConsumerGroupsResult deleteConsumerGroups(Collection groupIds, DeleteConsumerGroupsOptions options);
+
+ /**
+ * Delete consumer groups from the cluster with the default options.
+ *
+ * @return The DeleteConsumerGroupResult.
+ */
+ public DeleteConsumerGroupsResult deleteConsumerGroups(Collection groupIds) {
+ return deleteConsumerGroups(groupIds, new DeleteConsumerGroupsOptions());
+ }
}
diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/Config.java b/clients/src/main/java/org/apache/kafka/clients/admin/Config.java
index 56f9c27079295..c81c0b60274b0 100644
--- a/clients/src/main/java/org/apache/kafka/clients/admin/Config.java
+++ b/clients/src/main/java/org/apache/kafka/clients/admin/Config.java
@@ -21,39 +21,40 @@
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
/**
* A configuration object containing the configuration entries for a resource.
- *
+ *
* The API of this class is evolving, see {@link AdminClient} for details.
*/
@InterfaceStability.Evolving
public class Config {
- private final Collection entries;
+ private final Map entries = new HashMap<>();
/**
* Create a configuration instance with the provided entries.
*/
public Config(Collection entries) {
- this.entries = Collections.unmodifiableCollection(entries);
+ for (ConfigEntry entry : entries) {
+ this.entries.put(entry.name(), entry);
+ }
}
/**
* Configuration entries for a resource.
*/
public Collection entries() {
- return entries;
+ return Collections.unmodifiableCollection(entries.values());
}
/**
* Get the configuration entry with the provided name or null if there isn't one.
*/
public ConfigEntry get(String name) {
- for (ConfigEntry entry : entries)
- if (entry.name().equals(name))
- return entry;
- return null;
+ return entries.get(name);
}
@Override
@@ -75,6 +76,6 @@ public int hashCode() {
@Override
public String toString() {
- return "Config(entries=" + entries + ")";
+ return "Config(entries=" + entries.values() + ")";
}
}
diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/ConsumerGroupDescription.java b/clients/src/main/java/org/apache/kafka/clients/admin/ConsumerGroupDescription.java
new file mode 100644
index 0000000000000..933d66ca4b761
--- /dev/null
+++ b/clients/src/main/java/org/apache/kafka/clients/admin/ConsumerGroupDescription.java
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+package org.apache.kafka.clients.admin;
+
+import org.apache.kafka.common.ConsumerGroupState;
+import org.apache.kafka.common.Node;
+import org.apache.kafka.common.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+
+/**
+ * A detailed description of a single consumer group in the cluster.
+ */
+public class ConsumerGroupDescription {
+ private final String groupId;
+ private final boolean isSimpleConsumerGroup;
+ private final Collection members;
+ private final String partitionAssignor;
+ private final ConsumerGroupState state;
+ private final Node coordinator;
+
+ public ConsumerGroupDescription(String groupId,
+ boolean isSimpleConsumerGroup,
+ Collection members,
+ String partitionAssignor,
+ ConsumerGroupState state,
+ Node coordinator) {
+ this.groupId = groupId == null ? "" : groupId;
+ this.isSimpleConsumerGroup = isSimpleConsumerGroup;
+ this.members = members == null ? Collections.emptyList() :
+ Collections.unmodifiableList(new ArrayList<>(members));
+ this.partitionAssignor = partitionAssignor == null ? "" : partitionAssignor;
+ this.state = state;
+ this.coordinator = coordinator;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ConsumerGroupDescription that = (ConsumerGroupDescription) o;
+ return isSimpleConsumerGroup == that.isSimpleConsumerGroup &&
+ groupId.equals(that.groupId) &&
+ members.equals(that.members) &&
+ partitionAssignor.equals(that.partitionAssignor) &&
+ state.equals(that.state);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(isSimpleConsumerGroup, groupId, members, partitionAssignor, state);
+ }
+
+ /**
+ * The id of the consumer group.
+ */
+ public String groupId() {
+ return groupId;
+ }
+
+ /**
+ * If consumer group is simple or not.
+ */
+ public boolean isSimpleConsumerGroup() {
+ return isSimpleConsumerGroup;
+ }
+
+ /**
+ * A list of the members of the consumer group.
+ */
+ public Collection members() {
+ return members;
+ }
+
+ /**
+ * The consumer group partition assignor.
+ */
+ public String partitionAssignor() {
+ return partitionAssignor;
+ }
+
+ /**
+ * The consumer group state, or UNKNOWN if the state is too new for us to parse.
+ */
+ public ConsumerGroupState state() {
+ return state;
+ }
+
+ /**
+ * The consumer group coordinator, or null if the coordinator is not known.
+ */
+ public Node coordinator() {
+ return coordinator;
+ }
+
+ @Override
+ public String toString() {
+ return "(groupId=" + groupId +
+ ", isSimpleConsumerGroup=" + isSimpleConsumerGroup +
+ ", members=" + Utils.join(members, ",") +
+ ", partitionAssignor=" + partitionAssignor +
+ ", state=" + state +
+ ", coordinator=" + coordinator +
+ ")";
+ }
+}
diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/ConsumerGroupListing.java b/clients/src/main/java/org/apache/kafka/clients/admin/ConsumerGroupListing.java
new file mode 100644
index 0000000000000..46da9628010b6
--- /dev/null
+++ b/clients/src/main/java/org/apache/kafka/clients/admin/ConsumerGroupListing.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package org.apache.kafka.clients.admin;
+
+/**
+ * A listing of a consumer group in the cluster.
+ */
+public class ConsumerGroupListing {
+ private final String groupId;
+ private final boolean isSimpleConsumerGroup;
+
+ /**
+ * Create an instance with the specified parameters.
+ *
+ * @param groupId Group Id
+ * @param isSimpleConsumerGroup If consumer group is simple or not.
+ */
+ public ConsumerGroupListing(String groupId, boolean isSimpleConsumerGroup) {
+ this.groupId = groupId;
+ this.isSimpleConsumerGroup = isSimpleConsumerGroup;
+ }
+
+ /**
+ * Consumer Group Id
+ */
+ public String groupId() {
+ return groupId;
+ }
+
+ /**
+ * If Consumer Group is simple or not.
+ */
+ public boolean isSimpleConsumerGroup() {
+ return isSimpleConsumerGroup;
+ }
+
+ @Override
+ public String toString() {
+ return "(" +
+ "groupId='" + groupId + '\'' +
+ ", isSimpleConsumerGroup=" + isSimpleConsumerGroup +
+ ')';
+ }
+}
diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/CreateDelegationTokenOptions.java b/clients/src/main/java/org/apache/kafka/clients/admin/CreateDelegationTokenOptions.java
new file mode 100644
index 0000000000000..1b77b943800ec
--- /dev/null
+++ b/clients/src/main/java/org/apache/kafka/clients/admin/CreateDelegationTokenOptions.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package org.apache.kafka.clients.admin;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.kafka.common.annotation.InterfaceStability;
+import org.apache.kafka.common.security.auth.KafkaPrincipal;
+
+/**
+ * Options for {@link AdminClient#createDelegationToken(CreateDelegationTokenOptions)}.
+ *
+ * The API of this class is evolving, see {@link AdminClient} for details.
+ */
+@InterfaceStability.Evolving
+public class CreateDelegationTokenOptions extends AbstractOptions {
+ private long maxLifeTimeMs = -1;
+ private List renewers = new LinkedList<>();
+
+ public CreateDelegationTokenOptions renewers(List renewers) {
+ this.renewers = renewers;
+ return this;
+ }
+
+ public List renewers() {
+ return renewers;
+ }
+
+ public CreateDelegationTokenOptions maxlifeTimeMs(long maxLifeTimeMs) {
+ this.maxLifeTimeMs = maxLifeTimeMs;
+ return this;
+ }
+
+ public long maxlifeTimeMs() {
+ return maxLifeTimeMs;
+ }
+}
diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/CreateDelegationTokenResult.java b/clients/src/main/java/org/apache/kafka/clients/admin/CreateDelegationTokenResult.java
new file mode 100644
index 0000000000000..043cbe87fef5c
--- /dev/null
+++ b/clients/src/main/java/org/apache/kafka/clients/admin/CreateDelegationTokenResult.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package org.apache.kafka.clients.admin;
+
+import org.apache.kafka.common.KafkaFuture;
+import org.apache.kafka.common.annotation.InterfaceStability;
+import org.apache.kafka.common.security.token.delegation.DelegationToken;
+
+/**
+ * The result of the {@link KafkaAdminClient#createDelegationToken(CreateDelegationTokenOptions)} call.
+ *
+ * The API of this class is evolving, see {@link AdminClient} for details.
+ */
+@InterfaceStability.Evolving
+public class CreateDelegationTokenResult {
+ private final KafkaFuture delegationToken;
+
+ CreateDelegationTokenResult(KafkaFuture delegationToken) {
+ this.delegationToken = delegationToken;
+ }
+
+ /**
+ * Returns a future which yields a delegation token
+ */
+ public KafkaFuture delegationToken() {
+ return delegationToken;
+ }
+}
diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/DeleteConsumerGroupsOptions.java b/clients/src/main/java/org/apache/kafka/clients/admin/DeleteConsumerGroupsOptions.java
new file mode 100644
index 0000000000000..cd505f4c11138
--- /dev/null
+++ b/clients/src/main/java/org/apache/kafka/clients/admin/DeleteConsumerGroupsOptions.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+package org.apache.kafka.clients.admin;
+
+import org.apache.kafka.common.annotation.InterfaceStability;
+
+import java.util.Collection;
+
+/**
+ * Options for the {@link AdminClient#deleteConsumerGroups(Collection)} call.
+ *
+ * The API of this class is evolving, see {@link AdminClient} for details.
+ */
+@InterfaceStability.Evolving
+public class DeleteConsumerGroupsOptions extends AbstractOptions {
+
+}
diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/DeleteConsumerGroupsResult.java b/clients/src/main/java/org/apache/kafka/clients/admin/DeleteConsumerGroupsResult.java
new file mode 100644
index 0000000000000..dd6835cf10cbc
--- /dev/null
+++ b/clients/src/main/java/org/apache/kafka/clients/admin/DeleteConsumerGroupsResult.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+package org.apache.kafka.clients.admin;
+
+import org.apache.kafka.common.KafkaFuture;
+import org.apache.kafka.common.annotation.InterfaceStability;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * The result of the {@link AdminClient#deleteConsumerGroups(Collection)} call.
+ *
+ * The API of this class is evolving, see {@link AdminClient} for details.
+ */
+@InterfaceStability.Evolving
+public class DeleteConsumerGroupsResult {
+ private final Map> futures;
+
+ DeleteConsumerGroupsResult(final Map> futures) {
+ this.futures = futures;
+ }
+
+ /**
+ * Return a map from group id to futures which can be used to check the status of
+ * individual deletions.
+ */
+ public Map> deletedGroups() {
+ return futures;
+ }
+
+ /**
+ * Return a future which succeeds only if all the consumer group deletions succeed.
+ */
+ public KafkaFuture all() {
+ return KafkaFuture.allOf(futures.values().toArray(new KafkaFuture[0]));
+ }
+}
diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/DescribeConsumerGroupsOptions.java b/clients/src/main/java/org/apache/kafka/clients/admin/DescribeConsumerGroupsOptions.java
new file mode 100644
index 0000000000000..7daff1a483be8
--- /dev/null
+++ b/clients/src/main/java/org/apache/kafka/clients/admin/DescribeConsumerGroupsOptions.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+package org.apache.kafka.clients.admin;
+
+import org.apache.kafka.common.annotation.InterfaceStability;
+
+import java.util.Collection;
+
+/**
+ * Options for {@link AdminClient#describeConsumerGroups(Collection, DescribeConsumerGroupsOptions)}.
+ *
+ * The API of this class is evolving, see {@link AdminClient} for details.
+ */
+@InterfaceStability.Evolving
+public class DescribeConsumerGroupsOptions extends AbstractOptions {
+}
\ No newline at end of file
diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/DescribeConsumerGroupsResult.java b/clients/src/main/java/org/apache/kafka/clients/admin/DescribeConsumerGroupsResult.java
new file mode 100644
index 0000000000000..8f0ebad4f7ac6
--- /dev/null
+++ b/clients/src/main/java/org/apache/kafka/clients/admin/DescribeConsumerGroupsResult.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+package org.apache.kafka.clients.admin;
+
+import org.apache.kafka.common.KafkaFuture;
+import org.apache.kafka.common.annotation.InterfaceStability;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+
+/**
+ * The result of the {@link KafkaAdminClient#describeConsumerGroups(Collection, DescribeConsumerGroupsOptions)}} call.
+ *
+ * The API of this class is evolving, see {@link AdminClient} for details.
+ */
+@InterfaceStability.Evolving
+public class DescribeConsumerGroupsResult {
+
+ private final Map> futures;
+
+ public DescribeConsumerGroupsResult(final Map> futures) {
+ this.futures = futures;
+ }
+
+ /**
+ * Return a map from group id to futures which yield group descriptions.
+ */
+ public Map> describedGroups() {
+ return futures;
+ }
+
+ /**
+ * Return a future which yields all ConsumerGroupDescription objects, if all the describes succeed.
+ */
+ public KafkaFuture