diff --git a/build.gradle b/build.gradle index 0b3c570ddf5ca..fab8059ad561f 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ buildscript { } plugins { - id 'com.diffplug.spotless' version '5.12.4' + id 'com.diffplug.spotless' version '5.12.5' id 'com.github.ben-manes.versions' version '0.38.0' id 'idea' id 'java-library' @@ -449,6 +449,14 @@ subprojects { } } + // remove test output from all test types + tasks.withType(Test).all { t -> + cleanTest { + delete t.reports.junitXml.destination + delete t.reports.html.destination + } + } + jar { from "$rootDir/LICENSE" from "$rootDir/NOTICE" diff --git a/checkstyle/suppressions.xml b/checkstyle/suppressions.xml index ff5414f67d3e5..a93581bf9f355 100644 --- a/checkstyle/suppressions.xml +++ b/checkstyle/suppressions.xml @@ -152,7 +152,7 @@ + files="(KafkaStreams|KStreamImpl|KTableImpl|InternalTopologyBuilder|StreamsPartitionAssignor).java"/> diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/AbortTransactionOptions.java b/clients/src/main/java/org/apache/kafka/clients/admin/AbortTransactionOptions.java index ad5827ac6d0f5..52dc6b10bb964 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/AbortTransactionOptions.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/AbortTransactionOptions.java @@ -20,4 +20,12 @@ @InterfaceStability.Evolving public class AbortTransactionOptions extends AbstractOptions { + + @Override + public String toString() { + return "AbortTransactionOptions(" + + "timeoutMs=" + timeoutMs + + ')'; + } + } diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/Admin.java b/clients/src/main/java/org/apache/kafka/clients/admin/Admin.java index fc9dea2ddb67c..3dd2acc33c0d0 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/Admin.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/Admin.java @@ -338,7 +338,7 @@ default DescribeClusterResult describeCluster() { * This operation is supported by brokers with version 0.11.0.0 or higher. * * @param filter The filter to use. - * @return The DeleteAclsResult. + * @return The DescribeAclsResult. */ default DescribeAclsResult describeAcls(AclBindingFilter filter) { return describeAcls(filter, new DescribeAclsOptions()); @@ -354,7 +354,7 @@ default DescribeAclsResult describeAcls(AclBindingFilter filter) { * * @param filter The filter to use. * @param options The options to use when listing the ACLs. - * @return The DeleteAclsResult. + * @return The DescribeAclsResult. */ DescribeAclsResult describeAcls(AclBindingFilter filter, DescribeAclsOptions options); @@ -1533,6 +1533,28 @@ default AbortTransactionResult abortTransaction(AbortTransactionSpec spec) { */ AbortTransactionResult abortTransaction(AbortTransactionSpec spec, AbortTransactionOptions options); + /** + * List active transactions in the cluster. See + * {@link #listTransactions(ListTransactionsOptions)} for more details. + * + * @return The result + */ + default ListTransactionsResult listTransactions() { + return listTransactions(new ListTransactionsOptions()); + } + + /** + * List active transactions in the cluster. This will query all potential transaction + * coordinators in the cluster and collect the state of all transactions. Users + * should typically attempt to reduce the size of the result set using + * {@link ListTransactionsOptions#filterProducerIds(Collection)} or + * {@link ListTransactionsOptions#filterStates(Collection)} + * + * @param options Options to control the method behavior (including filters) + * @return The result + */ + ListTransactionsResult listTransactions(ListTransactionsOptions options); + /** * Get the metrics kept by the adminClient */ diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/DescribeProducersResult.java b/clients/src/main/java/org/apache/kafka/clients/admin/DescribeProducersResult.java index 473191661f349..55897bfc01c7c 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/DescribeProducersResult.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/DescribeProducersResult.java @@ -71,6 +71,13 @@ public PartitionProducerState(List activeProducers) { public List activeProducers() { return activeProducers; } + + @Override + public String toString() { + return "PartitionProducerState(" + + "activeProducers=" + activeProducers + + ')'; + } } } diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/DescribeTransactionsOptions.java b/clients/src/main/java/org/apache/kafka/clients/admin/DescribeTransactionsOptions.java index 3759b49f8abca..47beb7f343c62 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/DescribeTransactionsOptions.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/DescribeTransactionsOptions.java @@ -28,4 +28,11 @@ @InterfaceStability.Evolving public class DescribeTransactionsOptions extends AbstractOptions { + @Override + public String toString() { + return "DescribeTransactionsOptions(" + + "timeoutMs=" + timeoutMs + + ')'; + } + } diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/KafkaAdminClient.java b/clients/src/main/java/org/apache/kafka/clients/admin/KafkaAdminClient.java index e49f28a62f61b..6477bd060434e 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/KafkaAdminClient.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/KafkaAdminClient.java @@ -34,10 +34,14 @@ import org.apache.kafka.clients.admin.internals.AbortTransactionHandler; import org.apache.kafka.clients.admin.internals.AdminApiDriver; import org.apache.kafka.clients.admin.internals.AdminApiHandler; +import org.apache.kafka.clients.admin.internals.AdminApiFuture; import org.apache.kafka.clients.admin.internals.AdminMetadataManager; +import org.apache.kafka.clients.admin.internals.AllBrokersStrategy; import org.apache.kafka.clients.admin.internals.ConsumerGroupOperationContext; +import org.apache.kafka.clients.admin.internals.CoordinatorKey; import org.apache.kafka.clients.admin.internals.DescribeProducersHandler; import org.apache.kafka.clients.admin.internals.DescribeTransactionsHandler; +import org.apache.kafka.clients.admin.internals.ListTransactionsHandler; import org.apache.kafka.clients.admin.internals.MetadataOperationContext; import org.apache.kafka.clients.consumer.ConsumerPartitionAssignor.Assignment; import org.apache.kafka.clients.consumer.OffsetAndMetadata; @@ -4729,34 +4733,43 @@ void handleFailure(Throwable throwable) { @Override public DescribeProducersResult describeProducers(Collection topicPartitions, DescribeProducersOptions options) { - DescribeProducersHandler handler = new DescribeProducersHandler( - new HashSet<>(topicPartitions), - options, - logContext - ); - return new DescribeProducersResult(invokeDriver(handler, options.timeoutMs)); + AdminApiFuture.SimpleAdminApiFuture future = + DescribeProducersHandler.newFuture(topicPartitions); + DescribeProducersHandler handler = new DescribeProducersHandler(options, logContext); + invokeDriver(handler, future, options.timeoutMs); + return new DescribeProducersResult(future.all()); } @Override public DescribeTransactionsResult describeTransactions(Collection transactionalIds, DescribeTransactionsOptions options) { - DescribeTransactionsHandler handler = new DescribeTransactionsHandler( - transactionalIds, - logContext - ); - return new DescribeTransactionsResult(invokeDriver(handler, options.timeoutMs)); + AdminApiFuture.SimpleAdminApiFuture future = + DescribeTransactionsHandler.newFuture(transactionalIds); + DescribeTransactionsHandler handler = new DescribeTransactionsHandler(logContext); + invokeDriver(handler, future, options.timeoutMs); + return new DescribeTransactionsResult(future.all()); } @Override public AbortTransactionResult abortTransaction(AbortTransactionSpec spec, AbortTransactionOptions options) { - AbortTransactionHandler handler = new AbortTransactionHandler( - spec, - logContext - ); - return new AbortTransactionResult(invokeDriver(handler, options.timeoutMs)); + AdminApiFuture.SimpleAdminApiFuture future = + AbortTransactionHandler.newFuture(Collections.singleton(spec.topicPartition())); + AbortTransactionHandler handler = new AbortTransactionHandler(spec, logContext); + invokeDriver(handler, future, options.timeoutMs); + return new AbortTransactionResult(future.all()); + } + + @Override + public ListTransactionsResult listTransactions(ListTransactionsOptions options) { + AllBrokersStrategy.AllBrokersFuture> future = + ListTransactionsHandler.newFuture(); + ListTransactionsHandler handler = new ListTransactionsHandler(options, logContext); + invokeDriver(handler, future, options.timeoutMs); + return new ListTransactionsResult(future.all()); } - private Map> invokeDriver( + private void invokeDriver( AdminApiHandler handler, + AdminApiFuture future, Integer timeoutMs ) { long currentTimeMs = time.milliseconds(); @@ -4764,13 +4777,13 @@ private Map> invokeDriver( AdminApiDriver driver = new AdminApiDriver<>( handler, + future, deadlineMs, retryBackoffMs, logContext ); maybeSendRequests(driver, currentTimeMs); - return driver.futures(); } private void maybeSendRequests(AdminApiDriver driver, long currentTimeMs) { diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/ListTransactionsOptions.java b/clients/src/main/java/org/apache/kafka/clients/admin/ListTransactionsOptions.java new file mode 100644 index 0000000000000..3b2d90284d12b --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/clients/admin/ListTransactionsOptions.java @@ -0,0 +1,91 @@ +/* + * 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; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Options for {@link Admin#listTransactions()}. + * + * The API of this class is evolving, see {@link Admin} for details. + */ +@InterfaceStability.Evolving +public class ListTransactionsOptions extends AbstractOptions { + private Set filteredStates = Collections.emptySet(); + private Set filteredProducerIds = Collections.emptySet(); + + /** + * Filter only the transactions that are in a specific set of states. If no filter + * is specified or if the passed set of states is empty, then transactions in all + * states will be returned. + * + * @param states the set of states to filter by + * @return this object + */ + public ListTransactionsOptions filterStates(Collection states) { + this.filteredStates = new HashSet<>(states); + return this; + } + + /** + * Filter only the transactions from producers in a specific set of producerIds. + * If no filter is specified or if the passed collection of producerIds is empty, + * then the transactions of all producerIds will be returned. + * + * @param producerIdFilters the set of producerIds to filter by + * @return this object + */ + public ListTransactionsOptions filterProducerIds(Collection producerIdFilters) { + this.filteredProducerIds = new HashSet<>(producerIdFilters); + return this; + } + + /** + * Returns the set of states to be filtered or empty if no states have been specified. + * + * @return the current set of filtered states (empty means that no states are filtered and all + * all transactions will be returned) + */ + public Set filteredStates() { + return filteredStates; + } + + /** + * Returns the set of producerIds that are being filtered or empty if none have been specified. + * + * @return the current set of filtered states (empty means that no producerIds are filtered and + * all transactions will be returned) + */ + public Set filteredProducerIds() { + return filteredProducerIds; + } + + @Override + public String toString() { + return "ListTransactionsOptions(" + + "filteredStates=" + filteredStates + + ", filteredProducerIds=" + filteredProducerIds + + ", timeoutMs=" + timeoutMs + + ')'; + } +} diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/ListTransactionsResult.java b/clients/src/main/java/org/apache/kafka/clients/admin/ListTransactionsResult.java new file mode 100644 index 0000000000000..4b9d4eee1beeb --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/clients/admin/ListTransactionsResult.java @@ -0,0 +1,125 @@ +/* + * 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.internals.KafkaFutureImpl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The result of the {@link Admin#listTransactions()} call. + *

+ * The API of this class is evolving, see {@link Admin} for details. + */ +@InterfaceStability.Evolving +public class ListTransactionsResult { + private final KafkaFutureImpl>>> future; + + ListTransactionsResult(KafkaFutureImpl>>> future) { + this.future = future; + } + + /** + * Get all transaction listings. If any of the underlying requests fail, then the future + * returned from this method will also fail with the first encountered error. + * + * @return A future containing the collection of transaction listings. The future completes + * when all transaction listings are available and fails after any non-retriable error. + */ + public KafkaFuture> all() { + return allByBrokerId().thenApply(map -> { + List allListings = new ArrayList<>(); + for (Collection listings : map.values()) { + allListings.addAll(listings); + } + return allListings; + }); + } + + /** + * Get a future which returns a map containing the underlying listing future for each broker + * in the cluster. This is useful, for example, if a partial listing of transactions is + * sufficient, or if you want more granular error details. + * + * @return A future containing a map of futures by broker which complete individually when + * their respective transaction listings are available. The top-level future returned + * from this method may fail if the admin client is unable to lookup the available + * brokers in the cluster. + */ + public KafkaFuture>>> byBrokerId() { + KafkaFutureImpl>>> result = new KafkaFutureImpl<>(); + future.whenComplete((brokerFutures, exception) -> { + if (brokerFutures != null) { + Map>> brokerFuturesCopy = + new HashMap<>(brokerFutures.size()); + brokerFuturesCopy.putAll(brokerFutures); + result.complete(brokerFuturesCopy); + } else { + result.completeExceptionally(exception); + } + }); + return result; + } + + /** + * Get all transaction listings in a map which is keyed by the ID of respective broker + * that is currently managing them. If any of the underlying requests fail, then the future + * returned from this method will also fail with the first encountered error. + * + * @return A future containing a map from the broker ID to the transactions hosted by that + * broker respectively. This future completes when all transaction listings are + * available and fails after any non-retriable error. + */ + public KafkaFuture>> allByBrokerId() { + KafkaFutureImpl>> allFuture = new KafkaFutureImpl<>(); + Map> allListingsMap = new HashMap<>(); + + future.whenComplete((map, topLevelException) -> { + if (topLevelException != null) { + allFuture.completeExceptionally(topLevelException); + return; + } + + Set remainingResponses = new HashSet<>(map.keySet()); + map.forEach((brokerId, future) -> { + future.whenComplete((listings, brokerException) -> { + if (brokerException != null) { + allFuture.completeExceptionally(brokerException); + } else if (!allFuture.isDone()) { + allListingsMap.put(brokerId, listings); + remainingResponses.remove(brokerId); + + if (remainingResponses.isEmpty()) { + allFuture.complete(allListingsMap); + } + } + }); + }); + }); + + return allFuture; + } + +} diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/TransactionListing.java b/clients/src/main/java/org/apache/kafka/clients/admin/TransactionListing.java new file mode 100644 index 0000000000000..e8e7eb6143baf --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/clients/admin/TransactionListing.java @@ -0,0 +1,74 @@ +/* + * 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.Objects; + +@InterfaceStability.Evolving +public class TransactionListing { + private final String transactionalId; + private final long producerId; + private final TransactionState transactionState; + + public TransactionListing( + String transactionalId, + long producerId, + TransactionState transactionState + ) { + this.transactionalId = transactionalId; + this.producerId = producerId; + this.transactionState = transactionState; + } + + public String transactionalId() { + return transactionalId; + } + + public long producerId() { + return producerId; + } + + public TransactionState state() { + return transactionState; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TransactionListing that = (TransactionListing) o; + return producerId == that.producerId && + Objects.equals(transactionalId, that.transactionalId) && + transactionState == that.transactionState; + } + + @Override + public int hashCode() { + return Objects.hash(transactionalId, producerId, transactionState); + } + + @Override + public String toString() { + return "TransactionListing(" + + "transactionalId='" + transactionalId + '\'' + + ", producerId=" + producerId + + ", transactionState=" + transactionState + + ')'; + } +} diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AbortTransactionHandler.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AbortTransactionHandler.java index 09dc7be55162f..90fda93a5fa4e 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AbortTransactionHandler.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AbortTransactionHandler.java @@ -31,7 +31,6 @@ import org.apache.kafka.common.utils.LogContext; import org.slf4j.Logger; -import java.util.Collections; import java.util.List; import java.util.Set; @@ -52,17 +51,20 @@ public AbortTransactionHandler( this.lookupStrategy = new PartitionLeaderStrategy(logContext); } + public static AdminApiFuture.SimpleAdminApiFuture newFuture( + Set topicPartitions + ) { + return AdminApiFuture.forKeys(topicPartitions); + } + @Override public String apiName() { return "abortTransaction"; } @Override - public Keys initializeKeys() { - return Keys.dynamicMapped( - Collections.singleton(abortSpec.topicPartition()), - lookupStrategy - ); + public AdminApiLookupStrategy lookupStrategy() { + return lookupStrategy; } @Override diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiDriver.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiDriver.java index 45ab667db917c..a434f789e04e1 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiDriver.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiDriver.java @@ -16,15 +16,14 @@ */ package org.apache.kafka.clients.admin.internals; -import org.apache.kafka.clients.admin.internals.AdminApiHandler.Keys; import org.apache.kafka.common.errors.DisconnectException; -import org.apache.kafka.common.internals.KafkaFutureImpl; import org.apache.kafka.common.requests.AbstractRequest; import org.apache.kafka.common.requests.AbstractResponse; import org.apache.kafka.common.utils.LogContext; import org.slf4j.Logger; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -35,6 +34,8 @@ import java.util.OptionalInt; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; /** * The `KafkaAdminClient`'s internal `Call` primitive is not a good fit for multi-stage @@ -79,8 +80,7 @@ public class AdminApiDriver { private final long retryBackoffMs; private final long deadlineMs; private final AdminApiHandler handler; - private final Keys keys; - private final Map> futures; + private final AdminApiFuture future; private final BiMultimap lookupMap = new BiMultimap<>(); private final BiMultimap fulfillmentMap = new BiMultimap<>(); @@ -88,38 +88,23 @@ public class AdminApiDriver { public AdminApiDriver( AdminApiHandler handler, + AdminApiFuture future, long deadlineMs, long retryBackoffMs, LogContext logContext ) { this.handler = handler; + this.future = future; this.deadlineMs = deadlineMs; this.retryBackoffMs = retryBackoffMs; this.log = logContext.logger(AdminApiDriver.class); - this.futures = new HashMap<>(); - this.keys = initializeKeys(handler); - } - - private Keys initializeKeys(AdminApiHandler handler) { - Keys keys = handler.initializeKeys(); - - keys.staticKeys.forEach((key, brokerId) -> { - futures.put(key, new KafkaFutureImpl<>()); - map(key, brokerId); - }); - - keys.dynamicKeys.forEach(key -> { - futures.put(key, new KafkaFutureImpl<>()); - lookupMap.put(keys.lookupStrategy.lookupScope(key), key); - }); - - return keys; + retryLookup(future.lookupKeys()); } /** * Associate a key with a brokerId. This is called after a response in the Lookup - * stage reveals the mapping (e.g. when the `FindCoordinator` tells us the the - * group coordinator for a specific consumer group). + * stage reveals the mapping (e.g. when the `FindCoordinator` tells us the group + * coordinator for a specific consumer group). */ private void map(K key, Integer brokerId) { lookupMap.remove(key); @@ -131,26 +116,30 @@ private void map(K key, Integer brokerId) { * back to the Lookup stage, which will allow us to attempt lookup again. */ private void unmap(K key) { - if (!keys.dynamicKeys.contains(key)) { - throw new IllegalStateException("Attempt to unmap key " + key + " which is not dynamically mapped"); - } - fulfillmentMap.remove(key); - lookupMap.put(keys.lookupStrategy.lookupScope(key), key); + + ApiRequestScope lookupScope = handler.lookupStrategy().lookupScope(key); + OptionalInt destinationBrokerId = lookupScope.destinationBrokerId(); + + if (destinationBrokerId.isPresent()) { + fulfillmentMap.put(new FulfillmentScope(destinationBrokerId.getAsInt()), key); + } else { + lookupMap.put(handler.lookupStrategy().lookupScope(key), key); + } } - private void clear(K key) { - lookupMap.remove(key); - fulfillmentMap.remove(key); + private void clear(Collection keys) { + keys.forEach(key -> { + lookupMap.remove(key); + fulfillmentMap.remove(key); + }); } OptionalInt keyToBrokerId(K key) { Optional scope = fulfillmentMap.getKey(key); - if (scope.isPresent()) { - return OptionalInt.of(scope.get().destinationBrokerId); - } else { - return OptionalInt.empty(); - } + return scope + .map(fulfillmentScope -> OptionalInt.of(fulfillmentScope.destinationBrokerId)) + .orElseGet(OptionalInt::empty); } /** @@ -158,27 +147,39 @@ OptionalInt keyToBrokerId(K key) { * the key will be taken out of both the Lookup and Fulfillment stages so that request * are not retried. */ - private void completeExceptionally(K key, Throwable t) { - KafkaFutureImpl future = futures.get(key); - if (future == null) { - log.warn("Attempt to complete future for {}, which was not requested", key); - } else { - clear(key); - future.completeExceptionally(t); + private void completeExceptionally(Map errors) { + if (!errors.isEmpty()) { + future.completeExceptionally(errors); + clear(errors.keySet()); } } + private void completeLookupExceptionally(Map errors) { + if (!errors.isEmpty()) { + future.completeLookupExceptionally(errors); + clear(errors.keySet()); + } + } + + private void retryLookup(Collection keys) { + keys.forEach(this::unmap); + } + /** - * Complete the future associated with the given key. After is called, the key will + * Complete the future associated with the given key. After this is called, all keys will * be taken out of both the Lookup and Fulfillment stages so that request are not retried. */ - private void complete(K key, V value) { - KafkaFutureImpl future = futures.get(key); - if (future == null) { - log.warn("Attempt to complete future for {}, which was not requested", key); - } else { - clear(key); - future.complete(value); + private void complete(Map values) { + if (!values.isEmpty()) { + future.complete(values); + clear(values.keySet()); + } + } + + private void completeLookup(Map brokerIdMapping) { + if (!brokerIdMapping.isEmpty()) { + future.completeLookup(brokerIdMapping); + brokerIdMapping.forEach(this::map); } } @@ -197,13 +198,6 @@ public List> poll() { return requests; } - /** - * Get a map of the futures that are awaiting completion. - */ - public Map> futures() { - return futures; - } - /** * Callback that is invoked when a `Call` returns a response successfully. */ @@ -221,16 +215,18 @@ public void onResponse( spec.keys, response ); - result.completedKeys.forEach(this::complete); - result.failedKeys.forEach(this::completeExceptionally); - result.unmappedKeys.forEach(this::unmap); + complete(result.completedKeys); + completeExceptionally(result.failedKeys); + retryLookup(result.unmappedKeys); } else { - AdminApiLookupStrategy.LookupResult result = keys.lookupStrategy.handleResponse( + AdminApiLookupStrategy.LookupResult result = handler.lookupStrategy().handleResponse( spec.keys, response ); - result.failedKeys.forEach(this::completeExceptionally); - result.mappedKeys.forEach(this::map); + + result.completedKeys.forEach(lookupMap::remove); + completeLookup(result.mappedKeys); + completeLookupExceptionally(result.failedKeys); } } @@ -248,13 +244,23 @@ public void onFailure( "Will attempt retry", spec.request); // After a disconnect, we want the driver to attempt to lookup the key - // again (if the key is dynamically mapped). This gives us a chance to - // find a new coordinator or partition leader for example. - spec.keys.stream() - .filter(keys.dynamicKeys::contains) - .forEach(this::unmap); + // again. This gives us a chance to find a new coordinator or partition + // leader for example. + Set keysToUnmap = spec.keys.stream() + .filter(future.lookupKeys()::contains) + .collect(Collectors.toSet()); + retryLookup(keysToUnmap); } else { - spec.keys.forEach(key -> completeExceptionally(key, t)); + Map errors = spec.keys.stream().collect(Collectors.toMap( + Function.identity(), + key -> t + )); + + if (spec.scope instanceof FulfillmentScope) { + completeExceptionally(errors); + } else { + completeLookupExceptionally(errors); + } } } @@ -303,13 +309,11 @@ private void collectRequests( } private void collectLookupRequests(List> requests) { - if (!keys.dynamicKeys.isEmpty()) { - collectRequests( - requests, - lookupMap, - (keys, scope) -> this.keys.lookupStrategy.buildRequest(keys) - ); - } + collectRequests( + requests, + lookupMap, + (keys, scope) -> handler.lookupStrategy().buildRequest(keys) + ); } private void collectFulfillmentRequests(List> requests) { diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiFuture.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiFuture.java new file mode 100644 index 0000000000000..00fabb3d1cb64 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiFuture.java @@ -0,0 +1,125 @@ +/* + * 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.internals; + +import org.apache.kafka.common.internals.KafkaFutureImpl; + +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public interface AdminApiFuture { + + /** + * The initial set of lookup keys. Although this will usually match the fulfillment + * keys, it does not necessarily have to. For example, in the case of + * {@link AllBrokersStrategy.AllBrokersFuture}, + * we use the lookup phase in order to discover the set of keys that will be searched + * during the fulfillment phase. + * + * @return non-empty set of initial lookup keys + */ + Set lookupKeys(); + + /** + * Complete the futures associated with the given keys. + * + * @param values the completed keys with their respective values + */ + void complete(Map values); + + /** + * Invoked when lookup of a set of keys succeeds. + * + * @param brokerIdMapping the discovered mapping from key to the respective brokerId that will + * handle the fulfillment request + */ + default void completeLookup(Map brokerIdMapping) { + } + + /** + * Invoked when lookup fails with a fatal error on a set of keys. + * + * @param lookupErrors the set of keys that failed lookup with their respective errors + */ + default void completeLookupExceptionally(Map lookupErrors) { + completeExceptionally(lookupErrors); + } + + /** + * Complete the futures associated with the given keys exceptionally. + * + * @param errors the failed keys with their respective errors + */ + void completeExceptionally(Map errors); + + static SimpleAdminApiFuture forKeys(Set keys) { + return new SimpleAdminApiFuture<>(keys); + } + + /** + * This class can be used when the set of keys is known ahead of time. + */ + class SimpleAdminApiFuture implements AdminApiFuture { + private final Map> futures; + + public SimpleAdminApiFuture(Set keys) { + this.futures = keys.stream().collect(Collectors.toMap( + Function.identity(), + k -> new KafkaFutureImpl<>() + )); + } + + @Override + public Set lookupKeys() { + return futures.keySet(); + } + + @Override + public void complete(Map values) { + values.forEach(this::complete); + } + + private void complete(K key, V value) { + futureOrThrow(key).complete(value); + } + + @Override + public void completeExceptionally(Map errors) { + errors.forEach(this::completeExceptionally); + } + + private void completeExceptionally(K key, Throwable t) { + futureOrThrow(key).completeExceptionally(t); + } + + private KafkaFutureImpl futureOrThrow(K key) { + KafkaFutureImpl future = futures.get(key); + if (future == null) { + throw new IllegalArgumentException("Attempt to complete future for " + key + + ", which was not requested"); + } else { + return future; + } + } + + public Map> all() { + return futures; + } + } +} diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiHandler.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiHandler.java index 754ae89451fe0..db3c68e5cc2b1 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiHandler.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiHandler.java @@ -18,16 +18,12 @@ import org.apache.kafka.common.requests.AbstractRequest; import org.apache.kafka.common.requests.AbstractResponse; -import org.apache.kafka.common.utils.Utils; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import static java.util.Objects.requireNonNull; - public interface AdminApiHandler { /** @@ -35,19 +31,6 @@ public interface AdminApiHandler { */ String apiName(); - /** - * Initialize the set of keys required to handle this API and how the driver - * should map them to the broker that will handle the request for these keys. - * - * Two mapping types are supported: - * - * - Static mapping: when the brokerId is known ahead of time - * - Dynamic mapping: when the brokerId must be discovered dynamically - * - * @return the key mappings - */ - Keys initializeKeys(); - /** * Build the request. The set of keys are derived by {@link AdminApiDriver} * during the lookup stage as the set of keys which all map to the same @@ -82,44 +65,13 @@ public interface AdminApiHandler { */ ApiResult handleResponse(int brokerId, Set keys, AbstractResponse response); - class Keys { - public final Map staticKeys; - public final Set dynamicKeys; - public final AdminApiLookupStrategy lookupStrategy; - - public Keys( - Map staticKeys, - Set dynamicKeys, - AdminApiLookupStrategy lookupStrategy - ) { - this.staticKeys = requireNonNull(staticKeys); - this.dynamicKeys = requireNonNull(dynamicKeys); - this.lookupStrategy = lookupStrategy; - - Set staticAndDynamicKeys = Utils.intersection(HashSet::new, staticKeys.keySet(), dynamicKeys); - if (!staticAndDynamicKeys.isEmpty()) { - throw new IllegalArgumentException("The following keys were configured both as dynamically " + - "and statically mapped: " + staticAndDynamicKeys); - } - - if (!dynamicKeys.isEmpty()) { - requireNonNull(lookupStrategy); - } - } - - public static Keys staticMapped( - Map staticKeys - ) { - return new Keys<>(staticKeys, Collections.emptySet(), null); - } - - public static Keys dynamicMapped( - Set dynamicKeys, - AdminApiLookupStrategy lookupStrategy - ) { - return new Keys<>(Collections.emptyMap(), dynamicKeys, lookupStrategy); - } - } + /** + * Get the lookup strategy that is responsible for finding the brokerId + * which will handle each respective key. + * + * @return non-null lookup strategy + */ + AdminApiLookupStrategy lookupStrategy(); class ApiResult { public final Map completedKeys; @@ -159,6 +111,14 @@ public static ApiResult unmapped(List keys) { keys ); } + + public static ApiResult empty() { + return new ApiResult<>( + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyList() + ); + } } } diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiLookupStrategy.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiLookupStrategy.java index 7969d534a9762..56c0837135772 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiLookupStrategy.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminApiLookupStrategy.java @@ -20,6 +20,7 @@ import org.apache.kafka.common.requests.AbstractResponse; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; @@ -39,6 +40,11 @@ public interface AdminApiLookupStrategy { * single key. This can be supported by returning a different scope object * for each lookup key. * + * Note that if the {@link ApiRequestScope#destinationBrokerId()} maps to + * a specific brokerId, then lookup will be skipped. See the use of + * {@link StaticBrokerStrategy} in {@link DescribeProducersHandler} for + * an example of this usage. + * * @param key the lookup key * * @return request scope indicating how lookup requests can be batched together @@ -76,13 +82,31 @@ public interface AdminApiLookupStrategy { LookupResult handleResponse(Set keys, AbstractResponse response); class LookupResult { + // This is the set of keys that have been completed by the lookup phase itself. + // The driver will not attempt lookup or fulfillment for completed keys. + public final List completedKeys; + + // This is the set of keys that have been mapped to a specific broker for + // fulfillment of the API request. public final Map mappedKeys; + + // This is the set of keys that have encountered a fatal error during the lookup + // phase. The driver will not attempt lookup or fulfillment for failed keys. public final Map failedKeys; public LookupResult( Map failedKeys, Map mappedKeys ) { + this(Collections.emptyList(), failedKeys, mappedKeys); + } + + public LookupResult( + List completedKeys, + Map failedKeys, + Map mappedKeys + ) { + this.completedKeys = Collections.unmodifiableList(completedKeys); this.failedKeys = Collections.unmodifiableMap(failedKeys); this.mappedKeys = Collections.unmodifiableMap(mappedKeys); } diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminMetadataManager.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminMetadataManager.java index 6e834520c46f7..f1ccd9aeb5454 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminMetadataManager.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AdminMetadataManager.java @@ -38,7 +38,7 @@ * service thread (which also uses the NetworkClient). */ public class AdminMetadataManager { - private Logger log; + private final Logger log; /** * The minimum amount of time that we should wait between subsequent diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/AllBrokersStrategy.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AllBrokersStrategy.java new file mode 100644 index 0000000000000..56305db03d267 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/AllBrokersStrategy.java @@ -0,0 +1,212 @@ +/* + * 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.internals; + +import org.apache.kafka.common.internals.KafkaFutureImpl; +import org.apache.kafka.common.message.MetadataRequestData; +import org.apache.kafka.common.message.MetadataResponseData; +import org.apache.kafka.common.requests.AbstractResponse; +import org.apache.kafka.common.requests.MetadataRequest; +import org.apache.kafka.common.requests.MetadataResponse; +import org.apache.kafka.common.utils.LogContext; +import org.slf4j.Logger; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.OptionalInt; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * This class is used for use cases which require requests to be sent to all + * brokers in the cluster. + * + * This is a slightly degenerate case of a lookup strategy in the sense that + * the broker IDs are used as both the keys and values. Also, unlike + * {@link CoordinatorStrategy} and {@link PartitionLeaderStrategy}, we do not + * know the set of keys ahead of time: we require the initial lookup in order + * to discover what the broker IDs are. This is represented with a more complex + * type {@code Future>} in the admin API result type. + * For example, see {@link org.apache.kafka.clients.admin.ListTransactionsResult}. + */ +public class AllBrokersStrategy implements AdminApiLookupStrategy { + public static final BrokerKey ANY_BROKER = new BrokerKey(OptionalInt.empty()); + public static final Set LOOKUP_KEYS = Collections.singleton(ANY_BROKER); + private static final ApiRequestScope SINGLE_REQUEST_SCOPE = new ApiRequestScope() { + }; + + private final Logger log; + + public AllBrokersStrategy( + LogContext logContext + ) { + this.log = logContext.logger(AllBrokersStrategy.class); + } + + @Override + public ApiRequestScope lookupScope(BrokerKey key) { + return SINGLE_REQUEST_SCOPE; + } + + @Override + public MetadataRequest.Builder buildRequest(Set keys) { + validateLookupKeys(keys); + // Send empty `Metadata` request. We are only interested in the brokers from the response + return new MetadataRequest.Builder(new MetadataRequestData()); + } + + @Override + public LookupResult handleResponse(Set keys, AbstractResponse abstractResponse) { + validateLookupKeys(keys); + + MetadataResponse response = (MetadataResponse) abstractResponse; + MetadataResponseData.MetadataResponseBrokerCollection brokers = response.data().brokers(); + + if (brokers.isEmpty()) { + log.debug("Metadata response contained no brokers. Will backoff and retry"); + return LookupResult.empty(); + } else { + log.debug("Discovered all brokers {} to send requests to", brokers); + } + + Map brokerKeys = brokers.stream().collect(Collectors.toMap( + broker -> new BrokerKey(OptionalInt.of(broker.nodeId())), + MetadataResponseData.MetadataResponseBroker::nodeId + )); + + return new LookupResult<>( + Collections.singletonList(ANY_BROKER), + Collections.emptyMap(), + brokerKeys + ); + } + + private void validateLookupKeys(Set keys) { + if (keys.size() != 1) { + throw new IllegalArgumentException("Unexpected key set: " + keys); + } + BrokerKey key = keys.iterator().next(); + if (key != ANY_BROKER) { + throw new IllegalArgumentException("Unexpected key set: " + keys); + } + } + + public static class BrokerKey { + public final OptionalInt brokerId; + + public BrokerKey(OptionalInt brokerId) { + this.brokerId = brokerId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BrokerKey that = (BrokerKey) o; + return Objects.equals(brokerId, that.brokerId); + } + + @Override + public int hashCode() { + return Objects.hash(brokerId); + } + + @Override + public String toString() { + return "BrokerKey(" + + "brokerId=" + brokerId + + ')'; + } + } + + public static class AllBrokersFuture implements AdminApiFuture { + private final KafkaFutureImpl>> future = new KafkaFutureImpl<>(); + private final Map> brokerFutures = new HashMap<>(); + + @Override + public Set lookupKeys() { + return LOOKUP_KEYS; + } + + @Override + public void completeLookup(Map brokerMapping) { + brokerMapping.forEach((brokerKey, brokerId) -> { + if (brokerId != brokerKey.brokerId.orElse(-1)) { + throw new IllegalArgumentException("Invalid lookup mapping " + brokerKey + " -> " + brokerId); + } + brokerFutures.put(brokerId, new KafkaFutureImpl<>()); + }); + future.complete(brokerFutures); + } + + @Override + public void completeLookupExceptionally(Map lookupErrors) { + if (!LOOKUP_KEYS.equals(lookupErrors.keySet())) { + throw new IllegalArgumentException("Unexpected keys among lookup errors: " + lookupErrors); + } + future.completeExceptionally(lookupErrors.get(ANY_BROKER)); + } + + @Override + public void complete(Map values) { + values.forEach(this::complete); + } + + private void complete(AllBrokersStrategy.BrokerKey key, V value) { + if (key == ANY_BROKER) { + throw new IllegalArgumentException("Invalid attempt to complete with lookup key sentinel"); + } else { + futureOrThrow(key).complete(value); + } + } + + @Override + public void completeExceptionally(Map errors) { + errors.forEach(this::completeExceptionally); + } + + private void completeExceptionally(AllBrokersStrategy.BrokerKey key, Throwable t) { + if (key == ANY_BROKER) { + future.completeExceptionally(t); + } else { + futureOrThrow(key).completeExceptionally(t); + } + } + + public KafkaFutureImpl>> all() { + return future; + } + + private KafkaFutureImpl futureOrThrow(BrokerKey key) { + if (!key.brokerId.isPresent()) { + throw new IllegalArgumentException("Attempt to complete with invalid key: " + key); + } else { + int brokerId = key.brokerId.getAsInt(); + KafkaFutureImpl future = brokerFutures.get(brokerId); + if (future == null) { + throw new IllegalArgumentException("Attempt to complete with unknown broker id: " + brokerId); + } else { + return future; + } + } + } + + } + +} diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/ApiRequestScope.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/ApiRequestScope.java index a216e76049408..0d27f38b29f37 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/internals/ApiRequestScope.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/ApiRequestScope.java @@ -33,7 +33,11 @@ public interface ApiRequestScope { * Get the target broker ID that a request is intended for or * empty if the request can be sent to any broker. * - * @return optional broker id + * Note that if the destination broker ID is present in the + * {@link ApiRequestScope} returned by {@link AdminApiLookupStrategy#lookupScope(Object)}, + * then no lookup will be attempted. + * + * @return optional broker ID */ default OptionalInt destinationBrokerId() { return OptionalInt.empty(); diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/DescribeProducersHandler.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/DescribeProducersHandler.java index 2c62461d5c9f9..de1061f124dbe 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/internals/DescribeProducersHandler.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/DescribeProducersHandler.java @@ -31,12 +31,13 @@ import org.apache.kafka.common.requests.DescribeProducersResponse; import org.apache.kafka.common.utils.CollectionUtils; import org.apache.kafka.common.utils.LogContext; -import org.apache.kafka.common.utils.Utils; import org.slf4j.Logger; import java.util.ArrayList; +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.OptionalInt; @@ -45,20 +46,28 @@ import java.util.stream.Collectors; public class DescribeProducersHandler implements AdminApiHandler { - private final LogContext logContext; private final Logger log; private final DescribeProducersOptions options; - private final Set topicPartitions; + private final AdminApiLookupStrategy lookupStrategy; public DescribeProducersHandler( - Set topicPartitions, DescribeProducersOptions options, LogContext logContext ) { - this.topicPartitions = Collections.unmodifiableSet(topicPartitions); this.options = options; this.log = logContext.logger(DescribeProducersHandler.class); - this.logContext = logContext; + + if (options.brokerId().isPresent()) { + this.lookupStrategy = new StaticBrokerStrategy<>(options.brokerId().getAsInt()); + } else { + this.lookupStrategy = new PartitionLeaderStrategy(logContext); + } + } + + public static AdminApiFuture.SimpleAdminApiFuture newFuture( + Collection topicPartitions + ) { + return AdminApiFuture.forKeys(new HashSet<>(topicPartitions)); } @Override @@ -67,17 +76,8 @@ public String apiName() { } @Override - public Keys initializeKeys() { - if (options.brokerId().isPresent()) { - // If the options indicate a specific broker, then we can skip the lookup step - int destinationBrokerId = options.brokerId().getAsInt(); - Map staticMappedPartitions = - Utils.initializeMap(topicPartitions, () -> destinationBrokerId); - return Keys.staticMapped(staticMappedPartitions); - } else { - PartitionLeaderStrategy lookupStrategy = new PartitionLeaderStrategy(logContext); - return Keys.dynamicMapped(topicPartitions, lookupStrategy); - } + public AdminApiLookupStrategy lookupStrategy() { + return lookupStrategy; } @Override diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/DescribeTransactionsHandler.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/DescribeTransactionsHandler.java index edeaf1ccca41f..ac45f597f1ec8 100644 --- a/clients/src/main/java/org/apache/kafka/clients/admin/internals/DescribeTransactionsHandler.java +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/DescribeTransactionsHandler.java @@ -42,17 +42,20 @@ import java.util.stream.Collectors; public class DescribeTransactionsHandler implements AdminApiHandler { - private final LogContext logContext; private final Logger log; - private final Set keys; + private final AdminApiLookupStrategy lookupStrategy; public DescribeTransactionsHandler( - Collection transactionalIds, LogContext logContext ) { - this.keys = buildKeySet(transactionalIds); this.log = logContext.logger(DescribeTransactionsHandler.class); - this.logContext = logContext; + this.lookupStrategy = new CoordinatorStrategy(logContext); + } + + public static AdminApiFuture.SimpleAdminApiFuture newFuture( + Collection transactionalIds + ) { + return AdminApiFuture.forKeys(buildKeySet(transactionalIds)); } private static Set buildKeySet(Collection transactionalIds) { @@ -67,8 +70,8 @@ public String apiName() { } @Override - public Keys initializeKeys() { - return Keys.dynamicMapped(keys, new CoordinatorStrategy(logContext)); + public AdminApiLookupStrategy lookupStrategy() { + return lookupStrategy; } @Override diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/ListTransactionsHandler.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/ListTransactionsHandler.java new file mode 100644 index 0000000000000..d1b4ec2d5b8d6 --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/ListTransactionsHandler.java @@ -0,0 +1,130 @@ +/* + * 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.internals; + +import org.apache.kafka.clients.admin.ListTransactionsOptions; +import org.apache.kafka.clients.admin.TransactionListing; +import org.apache.kafka.clients.admin.TransactionState; +import org.apache.kafka.common.errors.CoordinatorNotAvailableException; +import org.apache.kafka.common.message.ListTransactionsRequestData; +import org.apache.kafka.common.protocol.Errors; +import org.apache.kafka.common.requests.AbstractResponse; +import org.apache.kafka.common.requests.ListTransactionsRequest; +import org.apache.kafka.common.requests.ListTransactionsResponse; +import org.apache.kafka.common.utils.LogContext; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class ListTransactionsHandler implements AdminApiHandler> { + private final Logger log; + private final ListTransactionsOptions options; + private final AllBrokersStrategy lookupStrategy; + + public ListTransactionsHandler( + ListTransactionsOptions options, + LogContext logContext + ) { + this.options = options; + this.log = logContext.logger(ListTransactionsHandler.class); + this.lookupStrategy = new AllBrokersStrategy(logContext); + } + + public static AllBrokersStrategy.AllBrokersFuture> newFuture() { + return new AllBrokersStrategy.AllBrokersFuture<>(); + } + + @Override + public String apiName() { + return "listTransactions"; + } + + @Override + public AdminApiLookupStrategy lookupStrategy() { + return lookupStrategy; + } + + @Override + public ListTransactionsRequest.Builder buildRequest( + int brokerId, + Set keys + ) { + ListTransactionsRequestData request = new ListTransactionsRequestData(); + request.setProducerIdFilters(new ArrayList<>(options.filteredProducerIds())); + request.setStateFilters(options.filteredStates().stream() + .map(TransactionState::toString) + .collect(Collectors.toList())); + return new ListTransactionsRequest.Builder(request); + } + + @Override + public ApiResult> handleResponse( + int brokerId, + Set keys, + AbstractResponse abstractResponse + ) { + AllBrokersStrategy.BrokerKey key = requireSingleton(keys, brokerId); + + ListTransactionsResponse response = (ListTransactionsResponse) abstractResponse; + Errors error = Errors.forCode(response.data().errorCode()); + + if (error == Errors.COORDINATOR_LOAD_IN_PROGRESS) { + log.debug("The `ListTransactions` request sent to broker {} failed because the " + + "coordinator is still loading state. Will try again after backing off", brokerId); + return ApiResult.empty(); + } else if (error == Errors.COORDINATOR_NOT_AVAILABLE) { + log.debug("The `ListTransactions` request sent to broker {} failed because the " + + "coordinator is shutting down", brokerId); + return ApiResult.failed(key, new CoordinatorNotAvailableException("ListTransactions " + + "request sent to broker " + brokerId + " failed because the coordinator is shutting down")); + } else if (error != Errors.NONE) { + log.error("The `ListTransactions` request sent to broker {} failed because of an " + + "unexpected error {}", brokerId, error); + return ApiResult.failed(key, error.exception("ListTransactions request " + + "sent to broker " + brokerId + " failed with an unexpected exception")); + } else { + List listings = response.data().transactionStates().stream() + .map(transactionState -> new TransactionListing( + transactionState.transactionalId(), + transactionState.producerId(), + TransactionState.parse(transactionState.transactionState()))) + .collect(Collectors.toList()); + return ApiResult.completed(key, listings); + } + } + + private AllBrokersStrategy.BrokerKey requireSingleton( + Set keys, + int brokerId + ) { + if (keys.size() != 1) { + throw new IllegalArgumentException("Unexpected key set: " + keys); + } + + AllBrokersStrategy.BrokerKey key = keys.iterator().next(); + if (!key.brokerId.isPresent() || key.brokerId.getAsInt() != brokerId) { + throw new IllegalArgumentException("Unexpected broker key: " + key); + } + + return key; + } + +} diff --git a/clients/src/main/java/org/apache/kafka/clients/admin/internals/StaticBrokerStrategy.java b/clients/src/main/java/org/apache/kafka/clients/admin/internals/StaticBrokerStrategy.java new file mode 100644 index 0000000000000..7b66537fe39dc --- /dev/null +++ b/clients/src/main/java/org/apache/kafka/clients/admin/internals/StaticBrokerStrategy.java @@ -0,0 +1,65 @@ +/* + * 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.internals; + +import org.apache.kafka.common.requests.AbstractRequest; +import org.apache.kafka.common.requests.AbstractResponse; + +import java.util.OptionalInt; +import java.util.Set; + +/** + * This lookup strategy is used when we already know the destination broker ID + * and we have no need for an explicit lookup. By setting {@link ApiRequestScope#destinationBrokerId()} + * in the returned value for {@link #lookupScope(Object)}, the driver will + * skip the lookup. + */ +public class StaticBrokerStrategy implements AdminApiLookupStrategy { + private final SingleBrokerScope scope; + + public StaticBrokerStrategy(int brokerId) { + this.scope = new SingleBrokerScope(brokerId); + } + + @Override + public ApiRequestScope lookupScope(K key) { + return scope; + } + + @Override + public AbstractRequest.Builder buildRequest(Set keys) { + throw new UnsupportedOperationException(); + } + + @Override + public LookupResult handleResponse(Set keys, AbstractResponse response) { + throw new UnsupportedOperationException(); + } + + private static class SingleBrokerScope implements ApiRequestScope { + private final int brokerId; + + private SingleBrokerScope(int brokerId) { + this.brokerId = brokerId; + } + + @Override + public OptionalInt destinationBrokerId() { + return OptionalInt.of(brokerId); + } + } +} diff --git a/clients/src/main/java/org/apache/kafka/clients/consumer/ConsumerInterceptor.java b/clients/src/main/java/org/apache/kafka/clients/consumer/ConsumerInterceptor.java index 6af47058e4e21..c04afccd8aaf9 100644 --- a/clients/src/main/java/org/apache/kafka/clients/consumer/ConsumerInterceptor.java +++ b/clients/src/main/java/org/apache/kafka/clients/consumer/ConsumerInterceptor.java @@ -65,7 +65,7 @@ public interface ConsumerInterceptor extends Configurable, AutoCloseable { * @param records records to be consumed by the client or records returned by the previous interceptors in the list. * @return records that are either modified by the interceptor or same as records passed to this method. */ - public ConsumerRecords onConsume(ConsumerRecords records); + ConsumerRecords onConsume(ConsumerRecords records); /** * This is called when offsets get committed. @@ -74,10 +74,10 @@ public interface ConsumerInterceptor extends Configurable, AutoCloseable { * * @param offsets A map of offsets by partition with associated metadata */ - public void onCommit(Map offsets); + void onCommit(Map offsets); /** * This is called when interceptor is closed */ - public void close(); + void close(); } diff --git a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/AbstractStickyAssignor.java b/clients/src/main/java/org/apache/kafka/clients/consumer/internals/AbstractStickyAssignor.java index 5798909927461..e111aa62599e4 100644 --- a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/AbstractStickyAssignor.java +++ b/clients/src/main/java/org/apache/kafka/clients/consumer/internals/AbstractStickyAssignor.java @@ -29,7 +29,6 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.Set; -import java.util.TreeMap; import java.util.TreeSet; import java.util.stream.Collectors; import org.apache.kafka.common.TopicPartition; @@ -40,6 +39,7 @@ public abstract class AbstractStickyAssignor extends AbstractPartitionAssignor { private static final Logger log = LoggerFactory.getLogger(AbstractStickyAssignor.class); public static final int DEFAULT_GENERATION = -1; + public int maxGeneration = DEFAULT_GENERATION; private PartitionMovements partitionMovements; @@ -77,12 +77,10 @@ public Map> assign(Map partitionsP partitionsTransferringOwnership = new HashMap<>(); return constrainedAssign(partitionsPerTopic, consumerToOwnedPartitions); } else { - log.debug("Detected that all not consumers were subscribed to same set of topics, falling back to the " + log.debug("Detected that not all consumers were subscribed to same set of topics, falling back to the " + "general case assignment algorithm"); partitionsTransferringOwnership = null; - // we don't need consumerToOwnedPartitions in general assign case - consumerToOwnedPartitions = null; - return generalAssign(partitionsPerTopic, subscriptions); + return generalAssign(partitionsPerTopic, subscriptions, consumerToOwnedPartitions); } } @@ -95,7 +93,7 @@ private boolean allSubscriptionsEqual(Set allTopics, Map> consumerToOwnedPartitions) { Set membersWithOldGeneration = new HashSet<>(); Set membersOfCurrentHighestGeneration = new HashSet<>(); - int maxGeneration = DEFAULT_GENERATION; + boolean isAllSubscriptionsEqual = true; Set subscribedTopics = new HashSet<>(); @@ -106,9 +104,9 @@ private boolean allSubscriptionsEqual(Set allTopics, // initialize the subscribed topics set if this is the first subscription if (subscribedTopics.isEmpty()) { subscribedTopics.addAll(subscription.topics()); - } else if (!(subscription.topics().size() == subscribedTopics.size() + } else if (isAllSubscriptionsEqual && !(subscription.topics().size() == subscribedTopics.size() && subscribedTopics.containsAll(subscription.topics()))) { - return false; + isAllSubscriptionsEqual = false; } MemberData memberData = memberData(subscription); @@ -141,7 +139,7 @@ private boolean allSubscriptionsEqual(Set allTopics, for (String consumer : membersWithOldGeneration) { consumerToOwnedPartitions.get(consumer).clear(); } - return true; + return isAllSubscriptionsEqual; } @@ -301,62 +299,6 @@ private Map> constrainedAssign(Map return assignment; } - /** - * get the unassigned partition list by computing the difference set of all sorted partitions - * and sortedAssignedPartitions. If no assigned partitions, we'll just return all topic partitions. - * - * To compute the difference set, we use two pointers technique here: - * - * We loop through the all sorted topics, and then iterate all partitions the topic has, - * compared with the ith element in sortedAssignedPartitions(i starts from 0): - * - if not equal to the ith element, add to unassignedPartitions - * - if equal to the the ith element, get next element from sortedAssignedPartitions - * - * @param totalPartitionsCount all partitions counts in this assignment - * @param partitionsPerTopic the number of partitions for each subscribed topic. - * @param sortedAssignedPartitions sorted partitions, all are included in the sortedPartitions - * @return the partitions not yet assigned to any consumers - */ - private List getUnassignedPartitions(int totalPartitionsCount, - Map partitionsPerTopic, - List sortedAssignedPartitions) { - List sortedAllTopics = new ArrayList<>(partitionsPerTopic.keySet()); - // sort all topics first, then we can have sorted all topic partitions by adding partitions starting from 0 - Collections.sort(sortedAllTopics); - - if (sortedAssignedPartitions.isEmpty()) { - // no assigned partitions means all partitions are unassigned partitions - return getAllTopicPartitions(partitionsPerTopic, sortedAllTopics, totalPartitionsCount); - } - - List unassignedPartitions = new ArrayList<>(totalPartitionsCount - sortedAssignedPartitions.size()); - - Collections.sort(sortedAssignedPartitions, Comparator.comparing(TopicPartition::topic).thenComparing(TopicPartition::partition)); - - boolean shouldAddDirectly = false; - Iterator sortedAssignedPartitionsIter = sortedAssignedPartitions.iterator(); - TopicPartition nextAssignedPartition = sortedAssignedPartitionsIter.next(); - - for (String topic : sortedAllTopics) { - int partitionCount = partitionsPerTopic.get(topic); - for (int i = 0; i < partitionCount; i++) { - if (shouldAddDirectly || !(nextAssignedPartition.topic().equals(topic) && nextAssignedPartition.partition() == i)) { - unassignedPartitions.add(new TopicPartition(topic, i)); - } else { - // this partition is in assignedPartitions, don't add to unassignedPartitions, just get next assigned partition - if (sortedAssignedPartitionsIter.hasNext()) { - nextAssignedPartition = sortedAssignedPartitionsIter.next(); - } else { - // add the remaining directly since there is no more sortedAssignedPartitions - shouldAddDirectly = true; - } - } - } - } - - return unassignedPartitions; - } - private List getAllTopicPartitions(Map partitionsPerTopic, List sortedAllTopics, @@ -384,37 +326,39 @@ private List getAllTopicPartitions(Map partitio * * @param partitionsPerTopic The number of partitions for each subscribed topic. * @param subscriptions Map from the member id to their respective topic subscription + * @param currentAssignment Each consumer's previously owned and still-subscribed partitions * * @return Map from each member to the list of partitions assigned to them. */ private Map> generalAssign(Map partitionsPerTopic, - Map subscriptions) { - Map> currentAssignment = new HashMap<>(); + Map subscriptions, + Map> currentAssignment) { + if (log.isDebugEnabled()) { + log.debug("performing general assign. partitionsPerTopic: {}, subscriptions: {}, currentAssignment: {}", + partitionsPerTopic, subscriptions, currentAssignment); + } + Map prevAssignment = new HashMap<>(); partitionMovements = new PartitionMovements(); - prepopulateCurrentAssignments(subscriptions, currentAssignment, prevAssignment); + prepopulateCurrentAssignments(subscriptions, prevAssignment); - // a mapping of all topic partitions to all consumers that can be assigned to them - final Map> partition2AllPotentialConsumers = new HashMap<>(); - // a mapping of all consumers to all potential topic partitions that can be assigned to them - final Map> consumer2AllPotentialPartitions = new HashMap<>(); + // a mapping of all topics to all consumers that can be assigned to them + final Map> topic2AllPotentialConsumers = new HashMap<>(partitionsPerTopic.keySet().size()); + // a mapping of all consumers to all potential topics that can be assigned to them + final Map> consumer2AllPotentialTopics = new HashMap<>(subscriptions.keySet().size()); - // initialize partition2AllPotentialConsumers and consumer2AllPotentialPartitions in the following two for loops - for (Entry entry: partitionsPerTopic.entrySet()) { - for (int i = 0; i < entry.getValue(); ++i) - partition2AllPotentialConsumers.put(new TopicPartition(entry.getKey(), i), new ArrayList<>()); - } + // initialize topic2AllPotentialConsumers and consumer2AllPotentialTopics + partitionsPerTopic.keySet().stream().forEach( + topicName -> topic2AllPotentialConsumers.put(topicName, new ArrayList<>())); for (Entry entry: subscriptions.entrySet()) { String consumerId = entry.getKey(); - consumer2AllPotentialPartitions.put(consumerId, new ArrayList<>()); + List subscribedTopics = new ArrayList<>(entry.getValue().topics().size()); + consumer2AllPotentialTopics.put(consumerId, subscribedTopics); entry.getValue().topics().stream().filter(topic -> partitionsPerTopic.get(topic) != null).forEach(topic -> { - for (int i = 0; i < partitionsPerTopic.get(topic); ++i) { - TopicPartition topicPartition = new TopicPartition(topic, i); - consumer2AllPotentialPartitions.get(consumerId).add(topicPartition); - partition2AllPotentialConsumers.get(topicPartition).add(consumerId); - } + subscribedTopics.add(topic); + topic2AllPotentialConsumers.get(topic).add(consumerId); }); // add this consumer to currentAssignment (with an empty topic partition assignment) if it does not already exist @@ -428,14 +372,18 @@ private Map> generalAssign(Map par for (TopicPartition topicPartition: entry.getValue()) currentPartitionConsumer.put(topicPartition, entry.getKey()); - List sortedPartitions = sortPartitions(partition2AllPotentialConsumers); + int totalPartitionsCount = partitionsPerTopic.values().stream().reduce(0, Integer::sum); + List sortedAllTopics = new ArrayList<>(topic2AllPotentialConsumers.keySet()); + Collections.sort(sortedAllTopics, new TopicComparator(topic2AllPotentialConsumers)); + List sortedAllPartitions = getAllTopicPartitions(partitionsPerTopic, sortedAllTopics, totalPartitionsCount); - // all partitions that need to be assigned (initially set to all partitions but adjusted in the following loop) - List unassignedPartitions = new ArrayList<>(sortedPartitions); + // the partitions already assigned in current assignment + List assignedPartitions = new ArrayList<>(); boolean revocationRequired = false; for (Iterator>> it = currentAssignment.entrySet().iterator(); it.hasNext();) { Map.Entry> entry = it.next(); - if (!subscriptions.containsKey(entry.getKey())) { + Subscription consumerSubscription = subscriptions.get(entry.getKey()); + if (consumerSubscription == null) { // if a consumer that existed before (and had some partition assignments) is now removed, remove it from currentAssignment for (TopicPartition topicPartition: entry.getValue()) currentPartitionConsumer.remove(topicPartition); @@ -444,23 +392,32 @@ private Map> generalAssign(Map par // otherwise (the consumer still exists) for (Iterator partitionIter = entry.getValue().iterator(); partitionIter.hasNext();) { TopicPartition partition = partitionIter.next(); - if (!partition2AllPotentialConsumers.containsKey(partition)) { - // if this topic partition of this consumer no longer exists remove it from currentAssignment of the consumer + if (!topic2AllPotentialConsumers.containsKey(partition.topic())) { + // if this topic partition of this consumer no longer exists, remove it from currentAssignment of the consumer partitionIter.remove(); currentPartitionConsumer.remove(partition); - } else if (!subscriptions.get(entry.getKey()).topics().contains(partition.topic())) { - // if this partition cannot remain assigned to its current consumer because the consumer - // is no longer subscribed to its topic remove it from currentAssignment of the consumer + } else if (!consumerSubscription.topics().contains(partition.topic())) { + // because the consumer is no longer subscribed to its topic, remove it from currentAssignment of the consumer partitionIter.remove(); revocationRequired = true; - } else + } else { // otherwise, remove the topic partition from those that need to be assigned only if // its current consumer is still subscribed to its topic (because it is already assigned // and we would want to preserve that assignment as much as possible) - unassignedPartitions.remove(partition); + assignedPartitions.add(partition); + } } } } + + // all partitions that needed to be assigned + List unassignedPartitions = getUnassignedPartitions(sortedAllPartitions, assignedPartitions, topic2AllPotentialConsumers); + assignedPartitions = null; + + if (log.isDebugEnabled()) { + log.debug("unassigned Partitions: {}", unassignedPartitions); + } + // at this point we have preserved all valid topic partition to consumer assignments and removed // all invalid topic partitions and invalid consumers. Now we need to assign unassignedPartitions // to consumers so that the topic partition assignments are as balanced as possible. @@ -469,58 +426,172 @@ private Map> generalAssign(Map par TreeSet sortedCurrentSubscriptions = new TreeSet<>(new SubscriptionComparator(currentAssignment)); sortedCurrentSubscriptions.addAll(currentAssignment.keySet()); - balance(currentAssignment, prevAssignment, sortedPartitions, unassignedPartitions, sortedCurrentSubscriptions, - consumer2AllPotentialPartitions, partition2AllPotentialConsumers, currentPartitionConsumer, revocationRequired); + balance(currentAssignment, prevAssignment, sortedAllPartitions, unassignedPartitions, sortedCurrentSubscriptions, + consumer2AllPotentialTopics, topic2AllPotentialConsumers, currentPartitionConsumer, revocationRequired, + partitionsPerTopic, totalPartitionsCount); + + if (log.isDebugEnabled()) { + log.debug("final assignment: {}", currentAssignment); + } + return currentAssignment; } + /** + * get the unassigned partition list by computing the difference set of the sortedPartitions(all partitions) + * and sortedAssignedPartitions. If no assigned partitions, we'll just return all sorted topic partitions. + * This is used in generalAssign method + * + * We loop the sortedPartition, and compare the ith element in sortedAssignedPartitions(i start from 0): + * - if not equal to the ith element, add to unassignedPartitions + * - if equal to the the ith element, get next element from sortedAssignedPartitions + * + * @param sortedAllPartitions: sorted all partitions + * @param sortedAssignedPartitions: sorted partitions, all are included in the sortedPartitions + * @param topic2AllPotentialConsumers: topics mapped to all consumers that subscribed to it + * @return partitions that aren't assigned to any current consumer + */ + private List getUnassignedPartitions(List sortedAllPartitions, + List sortedAssignedPartitions, + Map> topic2AllPotentialConsumers) { + if (sortedAssignedPartitions.isEmpty()) { + return sortedAllPartitions; + } + + List unassignedPartitions = new ArrayList<>(); + + Collections.sort(sortedAssignedPartitions, new PartitionComparator(topic2AllPotentialConsumers)); + + boolean shouldAddDirectly = false; + Iterator sortedAssignedPartitionsIter = sortedAssignedPartitions.iterator(); + TopicPartition nextAssignedPartition = sortedAssignedPartitionsIter.next(); + + for (TopicPartition topicPartition : sortedAllPartitions) { + if (shouldAddDirectly || !nextAssignedPartition.equals(topicPartition)) { + unassignedPartitions.add(topicPartition); + } else { + // this partition is in assignedPartitions, don't add to unassignedPartitions, just get next assigned partition + if (sortedAssignedPartitionsIter.hasNext()) { + nextAssignedPartition = sortedAssignedPartitionsIter.next(); + } else { + // add the remaining directly since there is no more sortedAssignedPartitions + shouldAddDirectly = true; + } + } + } + return unassignedPartitions; + } + + /** + * get the unassigned partition list by computing the difference set of all sorted partitions + * and sortedAssignedPartitions. If no assigned partitions, we'll just return all sorted topic partitions. + * This is used in constrainedAssign method + * + * To compute the difference set, we use two pointers technique here: + * + * We loop through the all sorted topics, and then iterate all partitions the topic has, + * compared with the ith element in sortedAssignedPartitions(i starts from 0): + * - if not equal to the ith element, add to unassignedPartitions + * - if equal to the the ith element, get next element from sortedAssignedPartitions + * + * @param totalPartitionsCount all partitions counts in this assignment + * @param partitionsPerTopic the number of partitions for each subscribed topic. + * @param sortedAssignedPartitions sorted partitions, all are included in the sortedPartitions + * @return the partitions not yet assigned to any consumers + */ + private List getUnassignedPartitions(int totalPartitionsCount, + Map partitionsPerTopic, + List sortedAssignedPartitions) { + List sortedAllTopics = new ArrayList<>(partitionsPerTopic.keySet()); + // sort all topics first, then we can have sorted all topic partitions by adding partitions starting from 0 + Collections.sort(sortedAllTopics); + + if (sortedAssignedPartitions.isEmpty()) { + // no assigned partitions means all partitions are unassigned partitions + return getAllTopicPartitions(partitionsPerTopic, sortedAllTopics, totalPartitionsCount); + } + + List unassignedPartitions = new ArrayList<>(totalPartitionsCount - sortedAssignedPartitions.size()); + + Collections.sort(sortedAssignedPartitions, Comparator.comparing(TopicPartition::topic).thenComparing(TopicPartition::partition)); + + boolean shouldAddDirectly = false; + Iterator sortedAssignedPartitionsIter = sortedAssignedPartitions.iterator(); + TopicPartition nextAssignedPartition = sortedAssignedPartitionsIter.next(); + + for (String topic : sortedAllTopics) { + int partitionCount = partitionsPerTopic.get(topic); + for (int i = 0; i < partitionCount; i++) { + if (shouldAddDirectly || !(nextAssignedPartition.topic().equals(topic) && nextAssignedPartition.partition() == i)) { + unassignedPartitions.add(new TopicPartition(topic, i)); + } else { + // this partition is in assignedPartitions, don't add to unassignedPartitions, just get next assigned partition + if (sortedAssignedPartitionsIter.hasNext()) { + nextAssignedPartition = sortedAssignedPartitionsIter.next(); + } else { + // add the remaining directly since there is no more sortedAssignedPartitions + shouldAddDirectly = true; + } + } + } + } + + return unassignedPartitions; + } + + /** + * update the prevAssignment with the partitions, consumer and generation in parameters + * + * @param partitions: The partitions to be updated the prevAssignement + * @param consumer: The consumer Id + * @param prevAssignment: The assignment contains the assignment with the 2nd largest generation + * @param generation: The generation of this assignment (partitions) + */ + private void updatePrevAssignment(Map prevAssignment, + List partitions, + String consumer, + int generation) { + for (TopicPartition partition: partitions) { + if (prevAssignment.containsKey(partition)) { + // only keep the latest previous assignment + if (generation > prevAssignment.get(partition).generation) { + prevAssignment.put(partition, new ConsumerGenerationPair(consumer, generation)); + } + } else { + prevAssignment.put(partition, new ConsumerGenerationPair(consumer, generation)); + } + } + } + + /** + * filling in the prevAssignment from the subscriptions. + * + * @param subscriptions: Map from the member id to their respective topic subscription + * @param prevAssignment: The assignment contains the assignment with the 2nd largest generation + */ private void prepopulateCurrentAssignments(Map subscriptions, - Map> currentAssignment, Map prevAssignment) { // we need to process subscriptions' user data with each consumer's reported generation in mind // higher generations overwrite lower generations in case of a conflict // note that a conflict could exists only if user data is for different generations - // for each partition we create a sorted map of its consumers by generation - Map> sortedPartitionConsumersByGeneration = new HashMap<>(); for (Map.Entry subscriptionEntry: subscriptions.entrySet()) { String consumer = subscriptionEntry.getKey(); - MemberData memberData = memberData(subscriptionEntry.getValue()); - - for (TopicPartition partition: memberData.partitions) { - if (sortedPartitionConsumersByGeneration.containsKey(partition)) { - Map consumers = sortedPartitionConsumersByGeneration.get(partition); - if (memberData.generation.isPresent() && consumers.containsKey(memberData.generation.get())) { - // same partition is assigned to two consumers during the same rebalance. - // log a warning and skip this record - log.warn("Partition '{}' is assigned to multiple consumers following sticky assignment generation {}.", - partition, memberData.generation); - } else - consumers.put(memberData.generation.orElse(DEFAULT_GENERATION), consumer); - } else { - TreeMap sortedConsumers = new TreeMap<>(); - sortedConsumers.put(memberData.generation.orElse(DEFAULT_GENERATION), consumer); - sortedPartitionConsumersByGeneration.put(partition, sortedConsumers); - } + Subscription subscription = subscriptionEntry.getValue(); + if (subscription.userData() != null) { + // since this is our 2nd time to deserialize memberData, rewind userData is necessary + subscription.userData().rewind(); } - } + MemberData memberData = memberData(subscriptionEntry.getValue()); - // prevAssignment holds the prior ConsumerGenerationPair (before current) of each partition - // current and previous consumers are the last two consumers of each partition in the above sorted map - for (Map.Entry> partitionConsumersEntry: sortedPartitionConsumersByGeneration.entrySet()) { - TopicPartition partition = partitionConsumersEntry.getKey(); - TreeMap consumers = partitionConsumersEntry.getValue(); - Iterator it = consumers.descendingKeySet().iterator(); - - // let's process the current (most recent) consumer first - String consumer = consumers.get(it.next()); - currentAssignment.computeIfAbsent(consumer, k -> new ArrayList<>()); - currentAssignment.get(consumer).add(partition); - - // now update previous assignment if any - if (it.hasNext()) { - int generation = it.next(); - prevAssignment.put(partition, new ConsumerGenerationPair(consumers.get(generation), generation)); + // we already have the maxGeneration info, so just compare the current generation of memberData, and put into prevAssignment + if (memberData.generation.isPresent() && memberData.generation.get() < maxGeneration) { + // if the current member's generation is lower than maxGeneration, put into prevAssignment if needed + updatePrevAssignment(prevAssignment, memberData.partitions, consumer, memberData.generation.get()); + } else if (!memberData.generation.isPresent() && maxGeneration > DEFAULT_GENERATION) { + // if maxGeneration is larger then DEFAULT_GENERATION + // put all (no generation) partitions as DEFAULT_GENERATION into prevAssignment if needed + updatePrevAssignment(prevAssignment, memberData.partitions, consumer, DEFAULT_GENERATION); } } } @@ -528,14 +599,18 @@ private void prepopulateCurrentAssignments(Map subscriptio /** * determine if the current assignment is a balanced one * - * @param currentAssignment the assignment whose balance needs to be checked - * @param sortedCurrentSubscriptions an ascending sorted set of consumers based on how many topic partitions are already assigned to them - * @param allSubscriptions a mapping of all consumers to all potential topic partitions that can be assigned to them + * @param currentAssignment: the assignment whose balance needs to be checked + * @param sortedCurrentSubscriptions: an ascending sorted set of consumers based on how many topic partitions are already assigned to them + * @param allSubscriptions: a mapping of all consumers to all potential topics that can be assigned to them + * @param partitionsPerTopic: The number of partitions for each subscribed topic + * @param totalPartitionCount total partition count to be assigned * @return true if the given assignment is balanced; false otherwise */ private boolean isBalanced(Map> currentAssignment, TreeSet sortedCurrentSubscriptions, - Map> allSubscriptions) { + Map> allSubscriptions, + Map partitionsPerTopic, + int totalPartitionCount) { int min = currentAssignment.get(sortedCurrentSubscriptions.first()).size(); int max = currentAssignment.get(sortedCurrentSubscriptions.last()).size(); if (min >= max - 1) @@ -561,19 +636,25 @@ private boolean isBalanced(Map> currentAssignment, int consumerPartitionCount = consumerPartitions.size(); // skip if this consumer already has all the topic partitions it can get - if (consumerPartitionCount == allSubscriptions.get(consumer).size()) + List allSubscribedTopics = allSubscriptions.get(consumer); + int maxAssignmentSize = getMaxAssignmentSize(totalPartitionCount, allSubscribedTopics, partitionsPerTopic); + + if (consumerPartitionCount == maxAssignmentSize) continue; // otherwise make sure it cannot get any more - List potentialTopicPartitions = allSubscriptions.get(consumer); - for (TopicPartition topicPartition: potentialTopicPartitions) { - if (!currentAssignment.get(consumer).contains(topicPartition)) { - String otherConsumer = allPartitions.get(topicPartition); - int otherConsumerPartitionCount = currentAssignment.get(otherConsumer).size(); - if (consumerPartitionCount < otherConsumerPartitionCount) { - log.debug("{} can be moved from consumer {} to consumer {} for a more balanced assignment.", - topicPartition, otherConsumer, consumer); - return false; + for (String topic: allSubscribedTopics) { + int partitionCount = partitionsPerTopic.get(topic); + for (int i = 0; i < partitionCount; i++) { + TopicPartition topicPartition = new TopicPartition(topic, i); + if (!currentAssignment.get(consumer).contains(topicPartition)) { + String otherConsumer = allPartitions.get(topicPartition); + int otherConsumerPartitionCount = currentAssignment.get(otherConsumer).size(); + if (consumerPartitionCount < otherConsumerPartitionCount) { + log.debug("{} can be moved from consumer {} to consumer {} for a more balanced assignment.", + topicPartition, otherConsumer, consumer); + return false; + } } } } @@ -581,6 +662,26 @@ private boolean isBalanced(Map> currentAssignment, return true; } + /** + * get the maximum assigned partition size of the {@code allSubscribedTopics} + * + * @param totalPartitionCount total partition count to be assigned + * @param allSubscribedTopics the subscribed topics of a consumer + * @param partitionsPerTopic The number of partitions for each subscribed topic + * @return maximum assigned partition size + */ + private int getMaxAssignmentSize(int totalPartitionCount, + List allSubscribedTopics, + Map partitionsPerTopic) { + int maxAssignmentSize; + if (allSubscribedTopics.size() == partitionsPerTopic.size()) { + maxAssignmentSize = totalPartitionCount; + } else { + maxAssignmentSize = allSubscribedTopics.stream().map(topic -> partitionsPerTopic.get(topic)).reduce(0, Integer::sum); + } + return maxAssignmentSize; + } + /** * @return the balance score of the given assignment, as the sum of assigned partitions size difference of all consumer pairs. * A perfectly balanced assignment (with all consumers getting the same number of partitions) has a balance score of 0. @@ -605,29 +706,16 @@ private int getBalanceScore(Map> assignment) { return score; } - /** - * Sort valid partitions so they are processed in the potential reassignment phase in the proper order - * that causes minimal partition movement among consumers (hence honoring maximal stickiness) - * - * @param partition2AllPotentialConsumers a mapping of partitions to their potential consumers - * @return an ascending sorted list of topic partitions based on how many consumers can potentially use them - */ - private List sortPartitions(Map> partition2AllPotentialConsumers) { - List sortedPartitions = new ArrayList<>(partition2AllPotentialConsumers.keySet()); - Collections.sort(sortedPartitions, new PartitionComparator(partition2AllPotentialConsumers)); - return sortedPartitions; - } - /** * The assignment should improve the overall balance of the partition assignments to consumers. */ private void assignPartition(TopicPartition partition, TreeSet sortedCurrentSubscriptions, Map> currentAssignment, - Map> consumer2AllPotentialPartitions, + Map> consumer2AllPotentialTopics, Map currentPartitionConsumer) { for (String consumer: sortedCurrentSubscriptions) { - if (consumer2AllPotentialPartitions.get(consumer).contains(partition)) { + if (consumer2AllPotentialTopics.get(consumer).contains(partition.topic())) { sortedCurrentSubscriptions.remove(consumer); currentAssignment.get(consumer).add(partition); currentPartitionConsumer.put(partition, consumer); @@ -637,19 +725,23 @@ private void assignPartition(TopicPartition partition, } } - private boolean canParticipateInReassignment(TopicPartition partition, - Map> partition2AllPotentialConsumers) { - // if a partition has two or more potential consumers it is subject to reassignment. - return partition2AllPotentialConsumers.get(partition).size() >= 2; + private boolean canParticipateInReassignment(String topic, + Map> topic2AllPotentialConsumers) { + // if a topic has two or more potential consumers it is subject to reassignment. + return topic2AllPotentialConsumers.get(topic).size() >= 2; } private boolean canParticipateInReassignment(String consumer, Map> currentAssignment, - Map> consumer2AllPotentialPartitions, - Map> partition2AllPotentialConsumers) { + Map> consumer2AllPotentialTopics, + Map> topic2AllPotentialConsumers, + Map partitionsPerTopic, + int totalPartitionCount) { List currentPartitions = currentAssignment.get(consumer); int currentAssignmentSize = currentPartitions.size(); - int maxAssignmentSize = consumer2AllPotentialPartitions.get(consumer).size(); + List allSubscribedTopics = consumer2AllPotentialTopics.get(consumer); + int maxAssignmentSize = getMaxAssignmentSize(totalPartitionCount, allSubscribedTopics, partitionsPerTopic); + if (currentAssignmentSize > maxAssignmentSize) log.error("The consumer {} is assigned more partitions than the maximum possible.", consumer); @@ -660,7 +752,7 @@ private boolean canParticipateInReassignment(String consumer, for (TopicPartition partition: currentPartitions) // if any of the partitions assigned to a consumer is subject to reassignment the consumer itself // is subject to reassignment - if (canParticipateInReassignment(partition, partition2AllPotentialConsumers)) + if (canParticipateInReassignment(partition.topic(), topic2AllPotentialConsumers)) return true; return false; @@ -674,36 +766,40 @@ private void balance(Map> currentAssignment, List sortedPartitions, List unassignedPartitions, TreeSet sortedCurrentSubscriptions, - Map> consumer2AllPotentialPartitions, - Map> partition2AllPotentialConsumers, + Map> consumer2AllPotentialTopics, + Map> topic2AllPotentialConsumers, Map currentPartitionConsumer, - boolean revocationRequired) { + boolean revocationRequired, + Map partitionsPerTopic, + int totalPartitionCount) { boolean initializing = currentAssignment.get(sortedCurrentSubscriptions.last()).isEmpty(); - boolean reassignmentPerformed = false; // assign all unassigned partitions for (TopicPartition partition: unassignedPartitions) { - // skip if there is no potential consumer for the partition - if (partition2AllPotentialConsumers.get(partition).isEmpty()) + // skip if there is no potential consumer for the topic + if (topic2AllPotentialConsumers.get(partition.topic()).isEmpty()) continue; assignPartition(partition, sortedCurrentSubscriptions, currentAssignment, - consumer2AllPotentialPartitions, currentPartitionConsumer); + consumer2AllPotentialTopics, currentPartitionConsumer); } // narrow down the reassignment scope to only those partitions that can actually be reassigned Set fixedPartitions = new HashSet<>(); - for (TopicPartition partition: partition2AllPotentialConsumers.keySet()) - if (!canParticipateInReassignment(partition, partition2AllPotentialConsumers)) - fixedPartitions.add(partition); + for (String topic: topic2AllPotentialConsumers.keySet()) + if (!canParticipateInReassignment(topic, topic2AllPotentialConsumers)) { + for (int i = 0; i < partitionsPerTopic.get(topic); i++) { + fixedPartitions.add(new TopicPartition(topic, i)); + } + } sortedPartitions.removeAll(fixedPartitions); unassignedPartitions.removeAll(fixedPartitions); // narrow down the reassignment scope to only those consumers that are subject to reassignment Map> fixedAssignments = new HashMap<>(); - for (String consumer: consumer2AllPotentialPartitions.keySet()) + for (String consumer: consumer2AllPotentialTopics.keySet()) if (!canParticipateInReassignment(consumer, currentAssignment, - consumer2AllPotentialPartitions, partition2AllPotentialConsumers)) { + consumer2AllPotentialTopics, topic2AllPotentialConsumers, partitionsPerTopic, totalPartitionCount)) { sortedCurrentSubscriptions.remove(consumer); fixedAssignments.put(consumer, currentAssignment.remove(consumer)); } @@ -715,11 +811,11 @@ private void balance(Map> currentAssignment, // if we don't already need to revoke something due to subscription changes, first try to balance by only moving newly added partitions if (!revocationRequired) { performReassignments(unassignedPartitions, currentAssignment, prevAssignment, sortedCurrentSubscriptions, - consumer2AllPotentialPartitions, partition2AllPotentialConsumers, currentPartitionConsumer); + consumer2AllPotentialTopics, topic2AllPotentialConsumers, currentPartitionConsumer, partitionsPerTopic, totalPartitionCount); } - reassignmentPerformed = performReassignments(sortedPartitions, currentAssignment, prevAssignment, sortedCurrentSubscriptions, - consumer2AllPotentialPartitions, partition2AllPotentialConsumers, currentPartitionConsumer); + boolean reassignmentPerformed = performReassignments(sortedPartitions, currentAssignment, prevAssignment, sortedCurrentSubscriptions, + consumer2AllPotentialTopics, topic2AllPotentialConsumers, currentPartitionConsumer, partitionsPerTopic, totalPartitionCount); // if we are not preserving existing assignments and we have made changes to the current assignment // make sure we are getting a more balanced assignment; otherwise, revert to previous assignment @@ -743,9 +839,11 @@ private boolean performReassignments(List reassignablePartitions Map> currentAssignment, Map prevAssignment, TreeSet sortedCurrentSubscriptions, - Map> consumer2AllPotentialPartitions, - Map> partition2AllPotentialConsumers, - Map currentPartitionConsumer) { + Map> consumer2AllPotentialTopics, + Map> topic2AllPotentialConsumers, + Map currentPartitionConsumer, + Map partitionsPerTopic, + int totalPartitionCount) { boolean reassignmentPerformed = false; boolean modified; @@ -755,11 +853,12 @@ private boolean performReassignments(List reassignablePartitions // reassign all reassignable partitions (starting from the partition with least potential consumers and if needed) // until the full list is processed or a balance is achieved Iterator partitionIterator = reassignablePartitions.iterator(); - while (partitionIterator.hasNext() && !isBalanced(currentAssignment, sortedCurrentSubscriptions, consumer2AllPotentialPartitions)) { + while (partitionIterator.hasNext() && !isBalanced(currentAssignment, sortedCurrentSubscriptions, + consumer2AllPotentialTopics, partitionsPerTopic, totalPartitionCount)) { TopicPartition partition = partitionIterator.next(); // the partition must have at least two consumers - if (partition2AllPotentialConsumers.get(partition).size() <= 1) + if (topic2AllPotentialConsumers.get(partition.topic()).size() <= 1) log.error("Expected more than one potential consumer for partition '{}'", partition); // the partition must have a current consumer @@ -776,9 +875,9 @@ private boolean performReassignments(List reassignablePartitions } // check if a better-suited consumer exist for the partition; if so, reassign it - for (String otherConsumer: partition2AllPotentialConsumers.get(partition)) { + for (String otherConsumer: topic2AllPotentialConsumers.get(partition.topic())) { if (currentAssignment.get(consumer).size() > currentAssignment.get(otherConsumer).size() + 1) { - reassignPartition(partition, currentAssignment, sortedCurrentSubscriptions, currentPartitionConsumer, consumer2AllPotentialPartitions); + reassignPartition(partition, currentAssignment, sortedCurrentSubscriptions, currentPartitionConsumer, consumer2AllPotentialTopics); reassignmentPerformed = true; modified = true; break; @@ -794,11 +893,11 @@ private void reassignPartition(TopicPartition partition, Map> currentAssignment, TreeSet sortedCurrentSubscriptions, Map currentPartitionConsumer, - Map> consumer2AllPotentialPartitions) { + Map> consumer2AllPotentialTopics) { // find the new consumer String newConsumer = null; for (String anotherConsumer: sortedCurrentSubscriptions) { - if (consumer2AllPotentialPartitions.get(anotherConsumer).contains(partition)) { + if (consumer2AllPotentialTopics.get(anotherConsumer).contains(partition.topic())) { newConsumer = anotherConsumer; break; } @@ -855,17 +954,35 @@ private Map> deepCopy(Map, Serializable { + private static final long serialVersionUID = 1L; + private Map> map; + + TopicComparator(Map> map) { + this.map = map; + } + + @Override + public int compare(String o1, String o2) { + int ret = map.get(o1).size() - map.get(o2).size(); + if (ret == 0) { + ret = o1.compareTo(o2); + } + return ret; + } + } + private static class PartitionComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; - private Map> map; + private Map> map; - PartitionComparator(Map> map) { + PartitionComparator(Map> map) { this.map = map; } @Override public int compare(TopicPartition o1, TopicPartition o2) { - int ret = map.get(o1).size() - map.get(o2).size(); + int ret = map.get(o1.topic()).size() - map.get(o2.topic()).size(); if (ret == 0) { ret = o1.topic().compareTo(o2.topic()); if (ret == 0) diff --git a/clients/src/main/java/org/apache/kafka/clients/producer/ProducerInterceptor.java b/clients/src/main/java/org/apache/kafka/clients/producer/ProducerInterceptor.java index f466547f4fce6..8f89d6faa9ab1 100644 --- a/clients/src/main/java/org/apache/kafka/clients/producer/ProducerInterceptor.java +++ b/clients/src/main/java/org/apache/kafka/clients/producer/ProducerInterceptor.java @@ -64,7 +64,7 @@ public interface ProducerInterceptor extends Configurable { * @param record the record from client or the record returned by the previous interceptor in the chain of interceptors. * @return producer record to send to topic/partition */ - public ProducerRecord onSend(ProducerRecord record); + ProducerRecord onSend(ProducerRecord record); /** * This method is called when the record sent to the server has been acknowledged, or when sending the record fails before @@ -86,10 +86,10 @@ public interface ProducerInterceptor extends Configurable { * {@link org.apache.kafka.clients.producer.KafkaProducer#send(ProducerRecord)}. * @param exception The exception thrown during processing of this record. Null if no error occurred. */ - public void onAcknowledgement(RecordMetadata metadata, Exception exception); + void onAcknowledgement(RecordMetadata metadata, Exception exception); /** * This is called when interceptor is closed */ - public void close(); + void close(); } diff --git a/clients/src/main/java/org/apache/kafka/common/compress/ZstdFactory.java b/clients/src/main/java/org/apache/kafka/common/compress/ZstdFactory.java index 8f4735e4d80da..4664f4e5656ec 100644 --- a/clients/src/main/java/org/apache/kafka/common/compress/ZstdFactory.java +++ b/clients/src/main/java/org/apache/kafka/common/compress/ZstdFactory.java @@ -17,6 +17,7 @@ package org.apache.kafka.common.compress; +import com.github.luben.zstd.BufferPool; import com.github.luben.zstd.RecyclingBufferPool; import com.github.luben.zstd.ZstdInputStreamNoFinalizer; import com.github.luben.zstd.ZstdOutputStreamNoFinalizer; @@ -47,10 +48,24 @@ public static OutputStream wrapForOutput(ByteBufferOutputStream buffer) { public static InputStream wrapForInput(ByteBuffer buffer, byte messageVersion, BufferSupplier decompressionBufferSupplier) { try { + // We use our own BufferSupplier instead of com.github.luben.zstd.RecyclingBufferPool since our + // implementation doesn't require locking or soft references. + BufferPool bufferPool = new BufferPool() { + @Override + public ByteBuffer get(int capacity) { + return decompressionBufferSupplier.get(capacity); + } + + @Override + public void release(ByteBuffer buffer) { + decompressionBufferSupplier.release(buffer); + } + }; + // Set output buffer (uncompressed) to 16 KB (none by default) to ensure reasonable performance // in cases where the caller reads a small number of bytes (potentially a single byte). return new BufferedInputStream(new ZstdInputStreamNoFinalizer(new ByteBufferInputStream(buffer), - RecyclingBufferPool.INSTANCE), 16 * 1024); + bufferPool), 16 * 1024); } catch (Throwable e) { throw new KafkaException(e); } diff --git a/clients/src/main/java/org/apache/kafka/common/config/AbstractConfig.java b/clients/src/main/java/org/apache/kafka/common/config/AbstractConfig.java index a25c3ea521d0c..b3c7e85d5b5bc 100644 --- a/clients/src/main/java/org/apache/kafka/common/config/AbstractConfig.java +++ b/clients/src/main/java/org/apache/kafka/common/config/AbstractConfig.java @@ -32,6 +32,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; /** * A convenient base class for configurations to extend. @@ -42,8 +43,12 @@ public class AbstractConfig { private final Logger log = LoggerFactory.getLogger(getClass()); - /* configs for which values have been requested, used to detect unused configs */ - private final Set used; + /** + * Configs for which values have been requested, used to detect unused configs. + * This set must be concurrent modifiable and iterable. It will be modified + * when directly accessed or as a result of RecordingMap access. + */ + private final Set used = ConcurrentHashMap.newKeySet(); /* the original values passed in by the user */ private final Map originals; @@ -106,7 +111,6 @@ public AbstractConfig(ConfigDef definition, Map originals, Map this.originals = resolveConfigVariables(configProviderProps, (Map) originals); this.values = definition.parse(this.originals); - this.used = Collections.synchronizedSet(new HashSet<>()); Map configUpdates = postProcessParsedConfig(Collections.unmodifiableMap(this.values)); for (Map.Entry update : configUpdates.entrySet()) { this.values.put(update.getKey(), update.getValue()); diff --git a/clients/src/main/java/org/apache/kafka/common/metrics/stats/Rate.java b/clients/src/main/java/org/apache/kafka/common/metrics/stats/Rate.java index 952b0fb323e85..c6b8574186a88 100644 --- a/clients/src/main/java/org/apache/kafka/common/metrics/stats/Rate.java +++ b/clients/src/main/java/org/apache/kafka/common/metrics/stats/Rate.java @@ -101,11 +101,4 @@ public String toString() { ", stat=" + stat + ')'; } - - /** - * @deprecated since 2.4 Use {@link WindowedSum} instead. - */ - @Deprecated - public static class SampledTotal extends WindowedSum { - } } diff --git a/clients/src/main/java/org/apache/kafka/common/protocol/ApiKeys.java b/clients/src/main/java/org/apache/kafka/common/protocol/ApiKeys.java index bd28aa26aa977..3f42ee9d6889f 100644 --- a/clients/src/main/java/org/apache/kafka/common/protocol/ApiKeys.java +++ b/clients/src/main/java/org/apache/kafka/common/protocol/ApiKeys.java @@ -108,7 +108,7 @@ public enum ApiKeys { UNREGISTER_BROKER(ApiMessageType.UNREGISTER_BROKER, false, RecordBatch.MAGIC_VALUE_V0, true), DESCRIBE_TRANSACTIONS(ApiMessageType.DESCRIBE_TRANSACTIONS), LIST_TRANSACTIONS(ApiMessageType.LIST_TRANSACTIONS), - ALLOCATE_PRODUCER_IDS(ApiMessageType.ALLOCATE_PRODUCER_IDS, true, false); + ALLOCATE_PRODUCER_IDS(ApiMessageType.ALLOCATE_PRODUCER_IDS, true, true); private static final Map> APIS_BY_LISTENER = new EnumMap<>(ApiMessageType.ListenerType.class); diff --git a/clients/src/main/java/org/apache/kafka/common/protocol/ByteBufferAccessor.java b/clients/src/main/java/org/apache/kafka/common/protocol/ByteBufferAccessor.java index 3c5c30973165f..bd0925d6db358 100644 --- a/clients/src/main/java/org/apache/kafka/common/protocol/ByteBufferAccessor.java +++ b/clients/src/main/java/org/apache/kafka/common/protocol/ByteBufferAccessor.java @@ -133,6 +133,11 @@ public long readVarlong() { return ByteUtils.readVarlong(buf); } + @Override + public int remaining() { + return buf.remaining(); + } + public void flip() { buf.flip(); } diff --git a/clients/src/main/java/org/apache/kafka/common/protocol/DataInputStreamReadable.java b/clients/src/main/java/org/apache/kafka/common/protocol/DataInputStreamReadable.java index 93c6c597d7102..70ed52d6a02f6 100644 --- a/clients/src/main/java/org/apache/kafka/common/protocol/DataInputStreamReadable.java +++ b/clients/src/main/java/org/apache/kafka/common/protocol/DataInputStreamReadable.java @@ -118,6 +118,15 @@ public long readVarlong() { } } + @Override + public int remaining() { + try { + return input.available(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @Override public void close() { try { diff --git a/clients/src/main/java/org/apache/kafka/common/protocol/Readable.java b/clients/src/main/java/org/apache/kafka/common/protocol/Readable.java index 46879cde53087..9c9e461ca806a 100644 --- a/clients/src/main/java/org/apache/kafka/common/protocol/Readable.java +++ b/clients/src/main/java/org/apache/kafka/common/protocol/Readable.java @@ -37,6 +37,7 @@ public interface Readable { ByteBuffer readByteBuffer(int length); int readVarint(); long readVarlong(); + int remaining(); default String readString(int length) { byte[] arr = new byte[length]; diff --git a/clients/src/main/java/org/apache/kafka/server/authorizer/Action.java b/clients/src/main/java/org/apache/kafka/server/authorizer/Action.java index a62b7f0965b2e..60af34b38f4bb 100644 --- a/clients/src/main/java/org/apache/kafka/server/authorizer/Action.java +++ b/clients/src/main/java/org/apache/kafka/server/authorizer/Action.java @@ -31,27 +31,32 @@ public class Action { private final boolean logIfAllowed; private final boolean logIfDenied; + /** + * @param operation non-null operation being performed + * @param resourcePattern non-null resource pattern on which this action is being performed + */ public Action(AclOperation operation, ResourcePattern resourcePattern, int resourceReferenceCount, boolean logIfAllowed, boolean logIfDenied) { - this.operation = operation; - this.resourcePattern = resourcePattern; + this.operation = Objects.requireNonNull(operation, "operation can't be null"); + this.resourcePattern = Objects.requireNonNull(resourcePattern, "resourcePattern can't be null"); this.logIfAllowed = logIfAllowed; this.logIfDenied = logIfDenied; this.resourceReferenceCount = resourceReferenceCount; } /** - * Resource on which action is being performed. + * @return a non-null resource pattern on which this action is being performed */ public ResourcePattern resourcePattern() { return resourcePattern; } /** - * Operation being performed. + * + * @return a non-null operation being performed */ public AclOperation operation() { return operation; diff --git a/clients/src/main/resources/common/message/AllocateProducerIdsRequest.json b/clients/src/main/resources/common/message/AllocateProducerIdsRequest.json index 0cfa494291a36..6f37313c3a6e4 100644 --- a/clients/src/main/resources/common/message/AllocateProducerIdsRequest.json +++ b/clients/src/main/resources/common/message/AllocateProducerIdsRequest.json @@ -16,7 +16,7 @@ { "apiKey": 67, "type": "request", - "listeners": ["controller", "zkBroker"], + "listeners": ["zkBroker", "broker", "controller"], "name": "AllocateProducerIdsRequest", "validVersions": "0", "flexibleVersions": "0+", diff --git a/clients/src/main/resources/common/message/InitProducerIdRequest.json b/clients/src/main/resources/common/message/InitProducerIdRequest.json index 5537aa95d3db8..4e75352db6f33 100644 --- a/clients/src/main/resources/common/message/InitProducerIdRequest.json +++ b/clients/src/main/resources/common/message/InitProducerIdRequest.json @@ -16,7 +16,7 @@ { "apiKey": 22, "type": "request", - "listeners": ["zkBroker"], + "listeners": ["zkBroker", "broker"], "name": "InitProducerIdRequest", // Version 1 is the same as version 0. // diff --git a/clients/src/test/java/org/apache/kafka/clients/admin/KafkaAdminClientTest.java b/clients/src/test/java/org/apache/kafka/clients/admin/KafkaAdminClientTest.java index 4cad255ba0241..0013d33d196ae 100644 --- a/clients/src/test/java/org/apache/kafka/clients/admin/KafkaAdminClientTest.java +++ b/clients/src/test/java/org/apache/kafka/clients/admin/KafkaAdminClientTest.java @@ -115,6 +115,7 @@ import org.apache.kafka.common.message.ListOffsetsResponseData; import org.apache.kafka.common.message.ListOffsetsResponseData.ListOffsetsTopicResponse; import org.apache.kafka.common.message.ListPartitionReassignmentsResponseData; +import org.apache.kafka.common.message.ListTransactionsResponseData; import org.apache.kafka.common.message.MetadataResponseData; import org.apache.kafka.common.message.MetadataResponseData.MetadataResponsePartition; import org.apache.kafka.common.message.MetadataResponseData.MetadataResponseTopic; @@ -173,6 +174,8 @@ import org.apache.kafka.common.requests.ListGroupsResponse; import org.apache.kafka.common.requests.ListOffsetsResponse; import org.apache.kafka.common.requests.ListPartitionReassignmentsResponse; +import org.apache.kafka.common.requests.ListTransactionsRequest; +import org.apache.kafka.common.requests.ListTransactionsResponse; import org.apache.kafka.common.requests.MetadataRequest; import org.apache.kafka.common.requests.MetadataResponse; import org.apache.kafka.common.requests.OffsetCommitResponse; @@ -5621,6 +5624,58 @@ public void testAbortTransactionFindLeaderAfterDisconnect() throws Exception { } } + @Test + public void testListTransactions() throws Exception { + try (AdminClientUnitTestEnv env = mockClientEnv()) { + MetadataResponseData.MetadataResponseBrokerCollection brokers = + new MetadataResponseData.MetadataResponseBrokerCollection(); + + env.cluster().nodes().forEach(node -> { + brokers.add(new MetadataResponseData.MetadataResponseBroker() + .setHost(node.host()) + .setNodeId(node.id()) + .setPort(node.port()) + .setRack(node.rack()) + ); + }); + + env.kafkaClient().prepareResponse( + request -> request instanceof MetadataRequest, + new MetadataResponse(new MetadataResponseData().setBrokers(brokers), + MetadataResponseData.HIGHEST_SUPPORTED_VERSION) + ); + + List expected = Arrays.asList( + new TransactionListing("foo", 12345L, TransactionState.ONGOING), + new TransactionListing("bar", 98765L, TransactionState.PREPARE_ABORT), + new TransactionListing("baz", 13579L, TransactionState.COMPLETE_COMMIT) + ); + assertEquals(Utils.mkSet(0, 1, 2), env.cluster().nodes().stream().map(Node::id) + .collect(Collectors.toSet())); + + env.cluster().nodes().forEach(node -> { + ListTransactionsResponseData response = new ListTransactionsResponseData() + .setErrorCode(Errors.NONE.code()); + + TransactionListing listing = expected.get(node.id()); + response.transactionStates().add(new ListTransactionsResponseData.TransactionState() + .setTransactionalId(listing.transactionalId()) + .setProducerId(listing.producerId()) + .setTransactionState(listing.state().toString()) + ); + + env.kafkaClient().prepareResponseFrom( + request -> request instanceof ListTransactionsRequest, + new ListTransactionsResponse(response), + node + ); + }); + + ListTransactionsResult result = env.adminClient().listTransactions(); + assertEquals(new HashSet<>(expected), new HashSet<>(result.all().get())); + } + } + private WriteTxnMarkersResponse writeTxnMarkersResponse( AbortTransactionSpec abortSpec, Errors error @@ -5773,7 +5828,7 @@ public void testClientSideTimeoutAfterFailureToReceiveResponse() throws Exceptio TestUtils.waitForCondition(() -> { time.sleep(1); return disconnectFuture.isDone(); - }, 1, 5000, () -> "Timed out waiting for expected disconnect"); + }, 5000, 1, () -> "Timed out waiting for expected disconnect"); assertFalse(disconnectFuture.isCompletedExceptionally()); assertFalse(result.future.isDone()); TestUtils.waitForCondition(env.kafkaClient()::hasInFlightRequests, diff --git a/clients/src/test/java/org/apache/kafka/clients/admin/ListTransactionsResultTest.java b/clients/src/test/java/org/apache/kafka/clients/admin/ListTransactionsResultTest.java new file mode 100644 index 0000000000000..769c50b8518c1 --- /dev/null +++ b/clients/src/test/java/org/apache/kafka/clients/admin/ListTransactionsResultTest.java @@ -0,0 +1,119 @@ +/* + * 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.KafkaException; +import org.apache.kafka.common.KafkaFuture; +import org.apache.kafka.common.internals.KafkaFutureImpl; +import org.apache.kafka.common.utils.Utils; +import org.junit.jupiter.api.Test; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.apache.kafka.test.TestUtils.assertFutureThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListTransactionsResultTest { + private final KafkaFutureImpl>>> future = + new KafkaFutureImpl<>(); + private final ListTransactionsResult result = new ListTransactionsResult(future); + + @Test + public void testAllFuturesFailIfLookupFails() { + future.completeExceptionally(new KafkaException()); + assertFutureThrows(result.all(), KafkaException.class); + assertFutureThrows(result.allByBrokerId(), KafkaException.class); + assertFutureThrows(result.byBrokerId(), KafkaException.class); + } + + @Test + public void testAllFuturesSucceed() throws Exception { + KafkaFutureImpl> future1 = new KafkaFutureImpl<>(); + KafkaFutureImpl> future2 = new KafkaFutureImpl<>(); + + Map>> brokerFutures = new HashMap<>(); + brokerFutures.put(1, future1); + brokerFutures.put(2, future2); + + future.complete(brokerFutures); + + List broker1Listings = asList( + new TransactionListing("foo", 12345L, TransactionState.ONGOING), + new TransactionListing("bar", 98765L, TransactionState.PREPARE_ABORT) + ); + future1.complete(broker1Listings); + + List broker2Listings = singletonList( + new TransactionListing("baz", 13579L, TransactionState.COMPLETE_COMMIT) + ); + future2.complete(broker2Listings); + + Map>> resultBrokerFutures = + result.byBrokerId().get(); + + assertEquals(Utils.mkSet(1, 2), resultBrokerFutures.keySet()); + assertEquals(broker1Listings, resultBrokerFutures.get(1).get()); + assertEquals(broker2Listings, resultBrokerFutures.get(2).get()); + assertEquals(broker1Listings, result.allByBrokerId().get().get(1)); + assertEquals(broker2Listings, result.allByBrokerId().get().get(2)); + + Set allExpected = new HashSet<>(); + allExpected.addAll(broker1Listings); + allExpected.addAll(broker2Listings); + + assertEquals(allExpected, new HashSet<>(result.all().get())); + } + + @Test + public void testPartialFailure() throws Exception { + KafkaFutureImpl> future1 = new KafkaFutureImpl<>(); + KafkaFutureImpl> future2 = new KafkaFutureImpl<>(); + + Map>> brokerFutures = new HashMap<>(); + brokerFutures.put(1, future1); + brokerFutures.put(2, future2); + + future.complete(brokerFutures); + + List broker1Listings = asList( + new TransactionListing("foo", 12345L, TransactionState.ONGOING), + new TransactionListing("bar", 98765L, TransactionState.PREPARE_ABORT) + ); + future1.complete(broker1Listings); + future2.completeExceptionally(new KafkaException()); + + Map>> resultBrokerFutures = + result.byBrokerId().get(); + + // Ensure that the future for broker 1 completes successfully + assertEquals(Utils.mkSet(1, 2), resultBrokerFutures.keySet()); + assertEquals(broker1Listings, resultBrokerFutures.get(1).get()); + + // Everything else should fail + assertFutureThrows(result.all(), KafkaException.class); + assertFutureThrows(result.allByBrokerId(), KafkaException.class); + assertFutureThrows(resultBrokerFutures.get(2), KafkaException.class); + } + +} diff --git a/clients/src/test/java/org/apache/kafka/clients/admin/MockAdminClient.java b/clients/src/test/java/org/apache/kafka/clients/admin/MockAdminClient.java index 369e1e6f55fe8..73e065f373a18 100644 --- a/clients/src/test/java/org/apache/kafka/clients/admin/MockAdminClient.java +++ b/clients/src/test/java/org/apache/kafka/clients/admin/MockAdminClient.java @@ -918,6 +918,11 @@ public AbortTransactionResult abortTransaction(AbortTransactionSpec spec, AbortT throw new UnsupportedOperationException("Not implemented yet"); } + @Override + public ListTransactionsResult listTransactions(ListTransactionsOptions options) { + throw new UnsupportedOperationException("Not implemented yet"); + } + @Override synchronized public void close(Duration timeout) {} diff --git a/clients/src/test/java/org/apache/kafka/clients/admin/internals/AdminApiDriverTest.java b/clients/src/test/java/org/apache/kafka/clients/admin/internals/AdminApiDriverTest.java index 06e72dfdb8182..a78b73a0fb645 100644 --- a/clients/src/test/java/org/apache/kafka/clients/admin/internals/AdminApiDriverTest.java +++ b/clients/src/test/java/org/apache/kafka/clients/admin/internals/AdminApiDriverTest.java @@ -18,7 +18,6 @@ import org.apache.kafka.clients.admin.internals.AdminApiDriver.RequestSpec; import org.apache.kafka.clients.admin.internals.AdminApiHandler.ApiResult; -import org.apache.kafka.clients.admin.internals.AdminApiHandler.Keys; import org.apache.kafka.clients.admin.internals.AdminApiLookupStrategy.LookupResult; import org.apache.kafka.common.errors.DisconnectException; import org.apache.kafka.common.errors.UnknownServerException; @@ -38,6 +37,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; import java.util.Set; @@ -57,11 +57,10 @@ class AdminApiDriverTest { @Test public void testCoalescedLookup() { - MockRequestScope scope = new MockRequestScope(OptionalInt.empty()); - TestContext ctx = new TestContext(dynamicMapped(map( - "foo", scope, - "bar", scope - ))); + TestContext ctx = TestContext.dynamicMapped(map( + "foo", "c1", + "bar", "c1" + )); Map, LookupResult> lookupRequests = map( mkSet("foo", "bar"), mapped("foo", 1, "bar", 2) @@ -81,10 +80,10 @@ public void testCoalescedLookup() { @Test public void testCoalescedFulfillment() { - TestContext ctx = new TestContext(dynamicMapped(map( - "foo", new MockRequestScope(OptionalInt.empty()), - "bar", new MockRequestScope(OptionalInt.of(1)) - ))); + TestContext ctx = TestContext.dynamicMapped(map( + "foo", "c1", + "bar", "c2" + )); Map, LookupResult> lookupRequests = map( mkSet("foo"), mapped("foo", 1), @@ -104,10 +103,10 @@ public void testCoalescedFulfillment() { @Test public void testKeyLookupFailure() { - TestContext ctx = new TestContext(dynamicMapped(map( - "foo", new MockRequestScope(OptionalInt.empty()), - "bar", new MockRequestScope(OptionalInt.of(1)) - ))); + TestContext ctx = TestContext.dynamicMapped(map( + "foo", "c1", + "bar", "c2" + )); Map, LookupResult> lookupRequests = map( mkSet("foo"), failedLookup("foo", new UnknownServerException()), @@ -127,10 +126,10 @@ public void testKeyLookupFailure() { @Test public void testKeyLookupRetry() { - TestContext ctx = new TestContext(dynamicMapped(map( - "foo", new MockRequestScope(OptionalInt.empty()), - "bar", new MockRequestScope(OptionalInt.of(1)) - ))); + TestContext ctx = TestContext.dynamicMapped(map( + "foo", "c1", + "bar", "c2" + )); Map, LookupResult> lookupRequests = map( mkSet("foo"), emptyLookup(), @@ -160,11 +159,11 @@ public void testKeyLookupRetry() { @Test public void testStaticMapping() { - TestContext ctx = new TestContext(Keys.staticMapped(map( + TestContext ctx = TestContext.staticMapped(map( "foo", 0, "bar", 1, "baz", 1 - ))); + )); Map, ApiResult> fulfillmentResults = map( mkSet("foo"), completed("foo", 15L), @@ -178,11 +177,11 @@ public void testStaticMapping() { @Test public void testFulfillmentFailure() { - TestContext ctx = new TestContext(Keys.staticMapped(map( + TestContext ctx = TestContext.staticMapped(map( "foo", 0, "bar", 1, "baz", 1 - ))); + )); Map, ApiResult> fulfillmentResults = map( mkSet("foo"), failed("foo", new UnknownServerException()), @@ -196,11 +195,11 @@ public void testFulfillmentFailure() { @Test public void testFulfillmentRetry() { - TestContext ctx = new TestContext(Keys.staticMapped(map( + TestContext ctx = TestContext.staticMapped(map( "foo", 0, "bar", 1, "baz", 1 - ))); + )); Map, ApiResult> fulfillmentResults = map( mkSet("foo"), completed("foo", 15L), @@ -220,10 +219,10 @@ public void testFulfillmentRetry() { @Test public void testFulfillmentUnmapping() { - TestContext ctx = new TestContext(dynamicMapped(map( - "foo", new MockRequestScope(OptionalInt.empty()), - "bar", new MockRequestScope(OptionalInt.of(1)) - ))); + TestContext ctx = TestContext.dynamicMapped(map( + "foo", "c1", + "bar", "c2" + )); Map, LookupResult> lookupRequests = map( mkSet("foo"), mapped("foo", 0), @@ -256,11 +255,10 @@ public void testFulfillmentUnmapping() { @Test public void testRecoalescedLookup() { - MockRequestScope scope = new MockRequestScope(OptionalInt.empty()); - TestContext ctx = new TestContext(dynamicMapped(map( - "foo", scope, - "bar", scope - ))); + TestContext ctx = TestContext.dynamicMapped(map( + "foo", "c1", + "bar", "c1" + )); Map, LookupResult> lookupRequests = map( mkSet("foo", "bar"), mapped("foo", 1, "bar", 2) @@ -292,9 +290,9 @@ public void testRecoalescedLookup() { @Test public void testRetryLookupAfterDisconnect() { - TestContext ctx = new TestContext(dynamicMapped(map( - "foo", new MockRequestScope(OptionalInt.empty()) - ))); + TestContext ctx = TestContext.dynamicMapped(map( + "foo", "c1" + )); int initialLeaderId = 1; @@ -303,7 +301,7 @@ public void testRetryLookupAfterDisconnect() { ); ctx.poll(initialLookup, emptyMap()); - assertMappedKey(ctx.driver, "foo", initialLeaderId); + assertMappedKey(ctx, "foo", initialLeaderId); ctx.handler.expectRequest(mkSet("foo"), completed("foo", 15L)); @@ -314,7 +312,7 @@ public void testRetryLookupAfterDisconnect() { assertEquals(OptionalInt.of(initialLeaderId), requestSpec.scope.destinationBrokerId()); ctx.driver.onFailure(ctx.time.milliseconds(), requestSpec, new DisconnectException()); - assertUnmappedKey(ctx.driver, "foo"); + assertUnmappedKey(ctx, "foo"); int retryLeaderId = 2; @@ -329,19 +327,18 @@ public void testRetryLookupAfterDisconnect() { @Test public void testCoalescedStaticAndDynamicFulfillment() { - Map dynamicLookupScopes = map( - "foo", new MockRequestScope(OptionalInt.empty()) + Map dynamicMapping = map( + "foo", "c1" ); Map staticMapping = map( "bar", 1 ); - TestContext ctx = new TestContext(new Keys<>( + TestContext ctx = new TestContext( staticMapping, - dynamicLookupScopes.keySet(), - new MockLookupStrategy<>(dynamicLookupScopes) - )); + dynamicMapping + ); // Initially we expect a lookup for the dynamic key and a // fulfillment request for the static key @@ -400,9 +397,9 @@ public void testCoalescedStaticAndDynamicFulfillment() { @Test public void testLookupRetryBookkeeping() { - TestContext ctx = new TestContext(dynamicMapped(map( - "foo", new MockRequestScope(OptionalInt.empty()) - ))); + TestContext ctx = TestContext.dynamicMapped(map( + "foo", "c1" + )); LookupResult emptyLookup = emptyLookup(); ctx.lookupStrategy().expectLookup(mkSet("foo"), emptyLookup); @@ -425,7 +422,7 @@ public void testLookupRetryBookkeeping() { @Test public void testFulfillmentRetryBookkeeping() { - TestContext ctx = new TestContext(Keys.staticMapped(map("foo", 0))); + TestContext ctx = TestContext.staticMapped(map("foo", 0)); ApiResult emptyFulfillment = emptyFulfillment(); ctx.handler.expectRequest(mkSet("foo"), emptyFulfillment); @@ -447,41 +444,41 @@ public void testFulfillmentRetryBookkeeping() { } private static void assertMappedKey( - AdminApiDriver driver, + TestContext context, String key, Integer expectedBrokerId ) { - OptionalInt brokerIdOpt = driver.keyToBrokerId(key); + OptionalInt brokerIdOpt = context.driver.keyToBrokerId(key); assertEquals(OptionalInt.of(expectedBrokerId), brokerIdOpt); } private static void assertUnmappedKey( - AdminApiDriver driver, + TestContext context, String key ) { - OptionalInt brokerIdOpt = driver.keyToBrokerId(key); + OptionalInt brokerIdOpt = context.driver.keyToBrokerId(key); assertEquals(OptionalInt.empty(), brokerIdOpt); - KafkaFutureImpl future = driver.futures().get(key); + KafkaFutureImpl future = context.future.all().get(key); assertFalse(future.isDone()); } private static void assertFailedKey( - AdminApiDriver driver, + TestContext context, String key, Throwable expectedException ) { - KafkaFutureImpl future = driver.futures().get(key); + KafkaFutureImpl future = context.future.all().get(key); assertTrue(future.isCompletedExceptionally()); Throwable exception = assertThrows(ExecutionException.class, future::get); assertEquals(expectedException, exception.getCause()); } private static void assertCompletedKey( - AdminApiDriver driver, + TestContext context, String key, Long expected ) { - KafkaFutureImpl future = driver.futures().get(key); + KafkaFutureImpl future = context.future.all().get(key); assertTrue(future.isDone()); try { assertEquals(expected, future.get()); @@ -492,48 +489,92 @@ private static void assertCompletedKey( private static class MockRequestScope implements ApiRequestScope { private final OptionalInt destinationBrokerId; + private final String id; - private MockRequestScope(OptionalInt destinationBrokerId) { + private MockRequestScope( + OptionalInt destinationBrokerId, + String id + ) { this.destinationBrokerId = destinationBrokerId; + this.id = id; } @Override public OptionalInt destinationBrokerId() { return destinationBrokerId; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MockRequestScope that = (MockRequestScope) o; + return Objects.equals(destinationBrokerId, that.destinationBrokerId) && + Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(destinationBrokerId, id); + } } private static class TestContext { private final MockTime time = new MockTime(); - private final Keys keys; private final MockAdminApiHandler handler; private final AdminApiDriver driver; + private final AdminApiFuture.SimpleAdminApiFuture future; + + public TestContext( + Map staticKeys, + Map dynamicKeys + ) { + Map lookupScopes = new HashMap<>(); + staticKeys.forEach((key, brokerId) -> { + MockRequestScope scope = new MockRequestScope(OptionalInt.of(brokerId), null); + lookupScopes.put(key, scope); + }); + + dynamicKeys.forEach((key, context) -> { + MockRequestScope scope = new MockRequestScope(OptionalInt.empty(), context); + lookupScopes.put(key, scope); + }); + + MockLookupStrategy lookupStrategy = new MockLookupStrategy<>(lookupScopes); + this.handler = new MockAdminApiHandler<>(lookupStrategy); + this.future = AdminApiFuture.forKeys(lookupStrategy.lookupScopes.keySet()); - public TestContext(Keys keys) { - this.keys = keys; - this.handler = new MockAdminApiHandler<>(keys); this.driver = new AdminApiDriver<>( handler, + future, time.milliseconds() + API_TIMEOUT_MS, RETRY_BACKOFF_MS, new LogContext() ); - keys.staticKeys.forEach((key, brokerId) -> { - assertMappedKey(driver, key, brokerId); + staticKeys.forEach((key, brokerId) -> { + assertMappedKey(this, key, brokerId); }); - keys.dynamicKeys.forEach(key -> { - assertUnmappedKey(driver, key); + dynamicKeys.keySet().forEach(key -> { + assertUnmappedKey(this, key); }); } + public static TestContext staticMapped(Map staticKeys) { + return new TestContext(staticKeys, Collections.emptyMap()); + } + + public static TestContext dynamicMapped(Map dynamicKeys) { + return new TestContext(Collections.emptyMap(), dynamicKeys); + } + private void assertLookupResponse( RequestSpec requestSpec, LookupResult result ) { requestSpec.keys.forEach(key -> { - assertUnmappedKey(driver, key); + assertUnmappedKey(this, key); }); // The response is just a placeholder. The result is all we are interested in @@ -542,11 +583,11 @@ private void assertLookupResponse( driver.onResponse(time.milliseconds(), requestSpec, response); result.mappedKeys.forEach((key, brokerId) -> { - assertMappedKey(driver, key, brokerId); + assertMappedKey(this, key, brokerId); }); result.failedKeys.forEach((key, exception) -> { - assertFailedKey(driver, key, exception); + assertFailedKey(this, key, exception); }); } @@ -558,7 +599,7 @@ private void assertResponse( new AssertionError("Fulfillment requests must specify a target brokerId")); requestSpec.keys.forEach(key -> { - assertMappedKey(driver, key, brokerId); + assertMappedKey(this, key, brokerId); }); // The response is just a placeholder. The result is all we are interested in @@ -568,23 +609,20 @@ private void assertResponse( driver.onResponse(time.milliseconds(), requestSpec, response); result.unmappedKeys.forEach(key -> { - assertUnmappedKey(driver, key); + assertUnmappedKey(this, key); }); result.failedKeys.forEach((key, exception) -> { - assertFailedKey(driver, key, exception); + assertFailedKey(this, key, exception); }); result.completedKeys.forEach((key, value) -> { - assertCompletedKey(driver, key, value); + assertCompletedKey(this, key, value); }); } private MockLookupStrategy lookupStrategy() { - if (keys.dynamicKeys.isEmpty()) { - throw new IllegalStateException("Unexpected lookup when no dynamic mapping is defined"); - } - return (MockLookupStrategy) keys.lookupStrategy; + return handler.lookupStrategy; } public void poll( @@ -656,11 +694,11 @@ public void reset() { } private static class MockAdminApiHandler implements AdminApiHandler { - private final Keys keyMappings; private final Map, ApiResult> expectedRequests = new HashMap<>(); + private final MockLookupStrategy lookupStrategy; - private MockAdminApiHandler(Keys keyMappings) { - this.keyMappings = keyMappings; + private MockAdminApiHandler(MockLookupStrategy lookupStrategy) { + this.lookupStrategy = lookupStrategy; } @Override @@ -669,8 +707,8 @@ public String apiName() { } @Override - public Keys initializeKeys() { - return keyMappings; + public AdminApiLookupStrategy lookupStrategy() { + return lookupStrategy; } public void expectRequest(Set keys, ApiResult result) { @@ -715,11 +753,6 @@ private static Map map(K k1, V v1, K k2, V v2, K k3, V v3) { return map; } - private static Keys dynamicMapped(Map lookupScopes) { - MockLookupStrategy strategy = new MockLookupStrategy<>(lookupScopes); - return Keys.dynamicMapped(lookupScopes.keySet(), strategy); - } - private static ApiResult completed(String key, Long value) { return new ApiResult<>(map(key, value), emptyMap(), Collections.emptyList()); } diff --git a/clients/src/test/java/org/apache/kafka/clients/admin/internals/AllBrokersStrategyIntegrationTest.java b/clients/src/test/java/org/apache/kafka/clients/admin/internals/AllBrokersStrategyIntegrationTest.java new file mode 100644 index 0000000000000..f4ec4680026e6 --- /dev/null +++ b/clients/src/test/java/org/apache/kafka/clients/admin/internals/AllBrokersStrategyIntegrationTest.java @@ -0,0 +1,247 @@ +/* + * 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.internals; + +import org.apache.kafka.common.errors.DisconnectException; +import org.apache.kafka.common.errors.UnknownServerException; +import org.apache.kafka.common.internals.KafkaFutureImpl; +import org.apache.kafka.common.message.MetadataRequestData; +import org.apache.kafka.common.message.MetadataResponseData; +import org.apache.kafka.common.protocol.ApiKeys; +import org.apache.kafka.common.requests.AbstractRequest; +import org.apache.kafka.common.requests.AbstractResponse; +import org.apache.kafka.common.requests.MetadataRequest; +import org.apache.kafka.common.requests.MetadataResponse; +import org.apache.kafka.common.utils.LogContext; +import org.apache.kafka.common.utils.MockTime; +import org.apache.kafka.common.utils.Utils; +import org.apache.kafka.test.TestUtils; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.OptionalInt; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AllBrokersStrategyIntegrationTest { + private static final long TIMEOUT_MS = 5000; + private static final long RETRY_BACKOFF_MS = 100; + + private final LogContext logContext = new LogContext(); + private final MockTime time = new MockTime(); + + private AdminApiDriver buildDriver( + AllBrokersStrategy.AllBrokersFuture result + ) { + return new AdminApiDriver<>( + new MockApiHandler(), + result, + time.milliseconds() + TIMEOUT_MS, + RETRY_BACKOFF_MS, + logContext + ); + } + + @Test + public void testFatalLookupError() { + AllBrokersStrategy.AllBrokersFuture result = new AllBrokersStrategy.AllBrokersFuture<>(); + AdminApiDriver driver = buildDriver(result); + + List> requestSpecs = driver.poll(); + assertEquals(1, requestSpecs.size()); + + AdminApiDriver.RequestSpec spec = requestSpecs.get(0); + assertEquals(AllBrokersStrategy.LOOKUP_KEYS, spec.keys); + + driver.onFailure(time.milliseconds(), spec, new UnknownServerException()); + assertTrue(result.all().isDone()); + TestUtils.assertFutureThrows(result.all(), UnknownServerException.class); + assertEquals(Collections.emptyList(), driver.poll()); + } + + @Test + public void testRetryLookupAfterDisconnect() { + AllBrokersStrategy.AllBrokersFuture result = new AllBrokersStrategy.AllBrokersFuture<>(); + AdminApiDriver driver = buildDriver(result); + + List> requestSpecs = driver.poll(); + assertEquals(1, requestSpecs.size()); + + AdminApiDriver.RequestSpec spec = requestSpecs.get(0); + assertEquals(AllBrokersStrategy.LOOKUP_KEYS, spec.keys); + + driver.onFailure(time.milliseconds(), spec, new DisconnectException()); + List> retrySpecs = driver.poll(); + assertEquals(1, retrySpecs.size()); + + AdminApiDriver.RequestSpec retrySpec = retrySpecs.get(0); + assertEquals(AllBrokersStrategy.LOOKUP_KEYS, retrySpec.keys); + assertEquals(time.milliseconds() + RETRY_BACKOFF_MS, retrySpec.nextAllowedTryMs); + assertEquals(Collections.emptyList(), driver.poll()); + } + + @Test + public void testMultiBrokerCompletion() throws Exception { + AllBrokersStrategy.AllBrokersFuture result = new AllBrokersStrategy.AllBrokersFuture<>(); + AdminApiDriver driver = buildDriver(result); + + List> lookupSpecs = driver.poll(); + assertEquals(1, lookupSpecs.size()); + AdminApiDriver.RequestSpec lookupSpec = lookupSpecs.get(0); + + Set brokerIds = Utils.mkSet(1, 2); + driver.onResponse(time.milliseconds(), lookupSpec, responseWithBrokers(brokerIds)); + assertTrue(result.all().isDone()); + + Map> brokerFutures = result.all().get(); + + List> requestSpecs = driver.poll(); + assertEquals(2, requestSpecs.size()); + + AdminApiDriver.RequestSpec requestSpec1 = requestSpecs.get(0); + assertTrue(requestSpec1.scope.destinationBrokerId().isPresent()); + int brokerId1 = requestSpec1.scope.destinationBrokerId().getAsInt(); + assertTrue(brokerIds.contains(brokerId1)); + + driver.onResponse(time.milliseconds(), requestSpec1, null); + KafkaFutureImpl future1 = brokerFutures.get(brokerId1); + assertTrue(future1.isDone()); + + AdminApiDriver.RequestSpec requestSpec2 = requestSpecs.get(1); + assertTrue(requestSpec2.scope.destinationBrokerId().isPresent()); + int brokerId2 = requestSpec2.scope.destinationBrokerId().getAsInt(); + assertNotEquals(brokerId1, brokerId2); + assertTrue(brokerIds.contains(brokerId2)); + + driver.onResponse(time.milliseconds(), requestSpec2, null); + KafkaFutureImpl future2 = brokerFutures.get(brokerId2); + assertTrue(future2.isDone()); + assertEquals(Collections.emptyList(), driver.poll()); + } + + @Test + public void testRetryFulfillmentAfterDisconnect() throws Exception { + AllBrokersStrategy.AllBrokersFuture result = new AllBrokersStrategy.AllBrokersFuture<>(); + AdminApiDriver driver = buildDriver(result); + + List> lookupSpecs = driver.poll(); + assertEquals(1, lookupSpecs.size()); + AdminApiDriver.RequestSpec lookupSpec = lookupSpecs.get(0); + + int brokerId = 1; + driver.onResponse(time.milliseconds(), lookupSpec, responseWithBrokers(Collections.singleton(brokerId))); + assertTrue(result.all().isDone()); + + Map> brokerFutures = result.all().get(); + KafkaFutureImpl future = brokerFutures.get(brokerId); + assertFalse(future.isDone()); + + List> requestSpecs = driver.poll(); + assertEquals(1, requestSpecs.size()); + AdminApiDriver.RequestSpec requestSpec = requestSpecs.get(0); + + driver.onFailure(time.milliseconds(), requestSpec, new DisconnectException()); + assertFalse(future.isDone()); + List> retrySpecs = driver.poll(); + assertEquals(1, retrySpecs.size()); + + AdminApiDriver.RequestSpec retrySpec = retrySpecs.get(0); + assertEquals(time.milliseconds() + RETRY_BACKOFF_MS, retrySpec.nextAllowedTryMs); + assertEquals(OptionalInt.of(brokerId), retrySpec.scope.destinationBrokerId()); + + driver.onResponse(time.milliseconds(), retrySpec, null); + assertTrue(future.isDone()); + assertEquals(brokerId, future.get()); + assertEquals(Collections.emptyList(), driver.poll()); + } + + @Test + public void testFatalFulfillmentError() throws Exception { + AllBrokersStrategy.AllBrokersFuture result = new AllBrokersStrategy.AllBrokersFuture<>(); + AdminApiDriver driver = buildDriver(result); + + List> lookupSpecs = driver.poll(); + assertEquals(1, lookupSpecs.size()); + AdminApiDriver.RequestSpec lookupSpec = lookupSpecs.get(0); + + int brokerId = 1; + driver.onResponse(time.milliseconds(), lookupSpec, responseWithBrokers(Collections.singleton(brokerId))); + assertTrue(result.all().isDone()); + + Map> brokerFutures = result.all().get(); + KafkaFutureImpl future = brokerFutures.get(brokerId); + assertFalse(future.isDone()); + + List> requestSpecs = driver.poll(); + assertEquals(1, requestSpecs.size()); + AdminApiDriver.RequestSpec requestSpec = requestSpecs.get(0); + + driver.onFailure(time.milliseconds(), requestSpec, new UnknownServerException()); + assertTrue(future.isDone()); + TestUtils.assertFutureThrows(future, UnknownServerException.class); + assertEquals(Collections.emptyList(), driver.poll()); + } + + private MetadataResponse responseWithBrokers(Set brokerIds) { + MetadataResponseData response = new MetadataResponseData(); + for (Integer brokerId : brokerIds) { + response.brokers().add(new MetadataResponseData.MetadataResponseBroker() + .setNodeId(brokerId) + .setHost("host" + brokerId) + .setPort(9092) + ); + } + return new MetadataResponse(response, ApiKeys.METADATA.latestVersion()); + } + + private class MockApiHandler implements AdminApiHandler { + private final AllBrokersStrategy allBrokersStrategy = new AllBrokersStrategy(logContext); + + @Override + public String apiName() { + return "mock-api"; + } + + @Override + public AbstractRequest.Builder buildRequest( + int brokerId, + Set keys + ) { + return new MetadataRequest.Builder(new MetadataRequestData()); + } + + @Override + public ApiResult handleResponse( + int brokerId, + Set keys, + AbstractResponse response + ) { + return ApiResult.completed(keys.iterator().next(), brokerId); + } + + @Override + public AdminApiLookupStrategy lookupStrategy() { + return allBrokersStrategy; + } + } +} diff --git a/clients/src/test/java/org/apache/kafka/clients/admin/internals/AllBrokersStrategyTest.java b/clients/src/test/java/org/apache/kafka/clients/admin/internals/AllBrokersStrategyTest.java new file mode 100644 index 0000000000000..8e4b961394cbe --- /dev/null +++ b/clients/src/test/java/org/apache/kafka/clients/admin/internals/AllBrokersStrategyTest.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.internals; + +import org.apache.kafka.common.message.MetadataResponseData; +import org.apache.kafka.common.protocol.ApiKeys; +import org.apache.kafka.common.requests.MetadataRequest; +import org.apache.kafka.common.requests.MetadataResponse; +import org.apache.kafka.common.utils.LogContext; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashSet; +import java.util.OptionalInt; +import java.util.Set; + +import static org.apache.kafka.common.utils.Utils.mkSet; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class AllBrokersStrategyTest { + private final LogContext logContext = new LogContext(); + + @Test + public void testBuildRequest() { + AllBrokersStrategy strategy = new AllBrokersStrategy(logContext); + MetadataRequest.Builder builder = strategy.buildRequest(AllBrokersStrategy.LOOKUP_KEYS); + assertEquals(Collections.emptyList(), builder.topics()); + } + + @Test + public void testBuildRequestWithInvalidLookupKeys() { + AllBrokersStrategy strategy = new AllBrokersStrategy(logContext); + AllBrokersStrategy.BrokerKey key1 = new AllBrokersStrategy.BrokerKey(OptionalInt.empty()); + AllBrokersStrategy.BrokerKey key2 = new AllBrokersStrategy.BrokerKey(OptionalInt.of(1)); + assertThrows(IllegalArgumentException.class, () -> strategy.buildRequest(mkSet(key1))); + assertThrows(IllegalArgumentException.class, () -> strategy.buildRequest(mkSet(key2))); + assertThrows(IllegalArgumentException.class, () -> strategy.buildRequest(mkSet(key1, key2))); + + Set keys = new HashSet<>(AllBrokersStrategy.LOOKUP_KEYS); + keys.add(key2); + assertThrows(IllegalArgumentException.class, () -> strategy.buildRequest(keys)); + } + + @Test + public void testHandleResponse() { + AllBrokersStrategy strategy = new AllBrokersStrategy(logContext); + + MetadataResponseData response = new MetadataResponseData(); + response.brokers().add(new MetadataResponseData.MetadataResponseBroker() + .setNodeId(1) + .setHost("host1") + .setPort(9092) + ); + response.brokers().add(new MetadataResponseData.MetadataResponseBroker() + .setNodeId(2) + .setHost("host2") + .setPort(9092) + ); + + AdminApiLookupStrategy.LookupResult lookupResult = strategy.handleResponse( + AllBrokersStrategy.LOOKUP_KEYS, + new MetadataResponse(response, ApiKeys.METADATA.latestVersion()) + ); + + assertEquals(Collections.emptyMap(), lookupResult.failedKeys); + + Set expectedMappedKeys = mkSet( + new AllBrokersStrategy.BrokerKey(OptionalInt.of(1)), + new AllBrokersStrategy.BrokerKey(OptionalInt.of(2)) + ); + + assertEquals(expectedMappedKeys, lookupResult.mappedKeys.keySet()); + lookupResult.mappedKeys.forEach((brokerKey, brokerId) -> { + assertEquals(OptionalInt.of(brokerId), brokerKey.brokerId); + }); + } + + @Test + public void testHandleResponseWithNoBrokers() { + AllBrokersStrategy strategy = new AllBrokersStrategy(logContext); + + MetadataResponseData response = new MetadataResponseData(); + + AdminApiLookupStrategy.LookupResult lookupResult = strategy.handleResponse( + AllBrokersStrategy.LOOKUP_KEYS, + new MetadataResponse(response, ApiKeys.METADATA.latestVersion()) + ); + + assertEquals(Collections.emptyMap(), lookupResult.failedKeys); + assertEquals(Collections.emptyMap(), lookupResult.mappedKeys); + } + + @Test + public void testHandleResponseWithInvalidLookupKeys() { + AllBrokersStrategy strategy = new AllBrokersStrategy(logContext); + AllBrokersStrategy.BrokerKey key1 = new AllBrokersStrategy.BrokerKey(OptionalInt.empty()); + AllBrokersStrategy.BrokerKey key2 = new AllBrokersStrategy.BrokerKey(OptionalInt.of(1)); + MetadataResponse response = new MetadataResponse(new MetadataResponseData(), ApiKeys.METADATA.latestVersion()); + + assertThrows(IllegalArgumentException.class, () -> strategy.handleResponse(mkSet(key1), response)); + assertThrows(IllegalArgumentException.class, () -> strategy.handleResponse(mkSet(key2), response)); + assertThrows(IllegalArgumentException.class, () -> strategy.handleResponse(mkSet(key1, key2), response)); + + Set keys = new HashSet<>(AllBrokersStrategy.LOOKUP_KEYS); + keys.add(key2); + assertThrows(IllegalArgumentException.class, () -> strategy.handleResponse(keys, response)); + } + +} diff --git a/clients/src/test/java/org/apache/kafka/clients/admin/internals/DescribeProducersHandlerTest.java b/clients/src/test/java/org/apache/kafka/clients/admin/internals/DescribeProducersHandlerTest.java index 34ebde7c7be48..d0ddb9960d75b 100644 --- a/clients/src/test/java/org/apache/kafka/clients/admin/internals/DescribeProducersHandlerTest.java +++ b/clients/src/test/java/org/apache/kafka/clients/admin/internals/DescribeProducersHandlerTest.java @@ -39,6 +39,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.OptionalInt; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -46,22 +47,18 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.apache.kafka.common.utils.Utils.mkSet; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; public class DescribeProducersHandlerTest { private DescribeProducersHandler newHandler( - Set topicPartitions, DescribeProducersOptions options ) { return new DescribeProducersHandler( - topicPartitions, options, new LogContext() ); @@ -77,16 +74,13 @@ public void testBrokerIdSetInOptions() { ); DescribeProducersHandler handler = newHandler( - topicPartitions, new DescribeProducersOptions().brokerId(brokerId) ); - AdminApiHandler.Keys keys = handler.initializeKeys(); - assertEquals(emptySet(), keys.dynamicKeys); - assertNull(keys.lookupStrategy); - - keys.staticKeys.forEach((topicPartition, mappedBrokerId) -> { - assertEquals(brokerId, mappedBrokerId, "Unexpected brokerId for " + topicPartition); + topicPartitions.forEach(topicPartition -> { + ApiRequestScope scope = handler.lookupStrategy().lookupScope(topicPartition); + assertEquals(OptionalInt.of(brokerId), scope.destinationBrokerId(), + "Unexpected brokerId for " + topicPartition); }); } @@ -99,14 +93,14 @@ public void testBrokerIdNotSetInOptions() { ); DescribeProducersHandler handler = newHandler( - topicPartitions, new DescribeProducersOptions() ); - AdminApiHandler.Keys keys = handler.initializeKeys(); - assertEquals(emptyMap(), keys.staticKeys); - assertTrue(keys.lookupStrategy instanceof PartitionLeaderStrategy); - assertEquals(topicPartitions, keys.dynamicKeys); + topicPartitions.forEach(topicPartition -> { + ApiRequestScope scope = handler.lookupStrategy().lookupScope(topicPartition); + assertEquals(OptionalInt.empty(), scope.destinationBrokerId(), + "Unexpected brokerId for " + topicPartition); + }); } @Test @@ -118,7 +112,6 @@ public void testBuildRequest() { ); DescribeProducersHandler handler = newHandler( - topicPartitions, new DescribeProducersOptions() ); @@ -197,7 +190,7 @@ public void testFatalNotLeaderErrorIfStaticMapped() { public void testCompletedResult() { TopicPartition topicPartition = new TopicPartition("foo", 5); DescribeProducersOptions options = new DescribeProducersOptions().brokerId(1); - DescribeProducersHandler handler = newHandler(mkSet(topicPartition), options); + DescribeProducersHandler handler = newHandler(options); int brokerId = 3; PartitionResponse partitionResponse = sampleProducerState(topicPartition); @@ -244,7 +237,7 @@ private ApiResult handleResponseWithErro TopicPartition topicPartition, Errors error ) { - DescribeProducersHandler handler = newHandler(mkSet(topicPartition), options); + DescribeProducersHandler handler = newHandler(options); int brokerId = options.brokerId().orElse(3); DescribeProducersResponse response = buildResponseWithError(topicPartition, error); return handler.handleResponse(brokerId, mkSet(topicPartition), response); diff --git a/clients/src/test/java/org/apache/kafka/clients/admin/internals/DescribeTransactionsHandlerTest.java b/clients/src/test/java/org/apache/kafka/clients/admin/internals/DescribeTransactionsHandlerTest.java index 04e7851e5f1a0..0db9cbb691781 100644 --- a/clients/src/test/java/org/apache/kafka/clients/admin/internals/DescribeTransactionsHandlerTest.java +++ b/clients/src/test/java/org/apache/kafka/clients/admin/internals/DescribeTransactionsHandlerTest.java @@ -48,7 +48,7 @@ public void testBuildRequest() { String transactionalId3 = "baz"; Set transactionalIds = mkSet(transactionalId1, transactionalId2, transactionalId3); - DescribeTransactionsHandler handler = new DescribeTransactionsHandler(transactionalIds, logContext); + DescribeTransactionsHandler handler = new DescribeTransactionsHandler(logContext); assertLookup(handler, transactionalIds); assertLookup(handler, mkSet(transactionalId1)); @@ -62,7 +62,7 @@ public void testHandleSuccessfulResponse() { String transactionalId2 = "bar"; Set transactionalIds = mkSet(transactionalId1, transactionalId2); - DescribeTransactionsHandler handler = new DescribeTransactionsHandler(transactionalIds, logContext); + DescribeTransactionsHandler handler = new DescribeTransactionsHandler(logContext); DescribeTransactionsResponseData.TransactionState transactionState1 = sampleTransactionState1(transactionalId1); @@ -86,8 +86,7 @@ public void testHandleSuccessfulResponse() { @Test public void testHandleErrorResponse() { String transactionalId = "foo"; - Set transactionalIds = mkSet(transactionalId); - DescribeTransactionsHandler handler = new DescribeTransactionsHandler(transactionalIds, logContext); + DescribeTransactionsHandler handler = new DescribeTransactionsHandler(logContext); assertFatalError(handler, transactionalId, Errors.TRANSACTIONAL_ID_AUTHORIZATION_FAILED); assertFatalError(handler, transactionalId, Errors.TRANSACTIONAL_ID_NOT_FOUND); assertFatalError(handler, transactionalId, Errors.UNKNOWN_SERVER_ERROR); diff --git a/clients/src/test/java/org/apache/kafka/clients/admin/internals/ListTransactionsHandlerTest.java b/clients/src/test/java/org/apache/kafka/clients/admin/internals/ListTransactionsHandlerTest.java new file mode 100644 index 0000000000000..799eb40ce8c1b --- /dev/null +++ b/clients/src/test/java/org/apache/kafka/clients/admin/internals/ListTransactionsHandlerTest.java @@ -0,0 +1,185 @@ +/* + * 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.internals; + +import org.apache.kafka.clients.admin.ListTransactionsOptions; +import org.apache.kafka.clients.admin.TransactionListing; +import org.apache.kafka.clients.admin.TransactionState; +import org.apache.kafka.clients.admin.internals.AdminApiHandler.ApiResult; +import org.apache.kafka.clients.admin.internals.AllBrokersStrategy.BrokerKey; +import org.apache.kafka.common.message.ListTransactionsResponseData; +import org.apache.kafka.common.protocol.Errors; +import org.apache.kafka.common.requests.ListTransactionsRequest; +import org.apache.kafka.common.requests.ListTransactionsResponse; +import org.apache.kafka.common.utils.LogContext; +import org.junit.jupiter.api.Test; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.OptionalInt; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class ListTransactionsHandlerTest { + private final LogContext logContext = new LogContext(); + + @Test + public void testBuildRequestWithoutFilters() { + int brokerId = 1; + BrokerKey brokerKey = new BrokerKey(OptionalInt.of(brokerId)); + ListTransactionsOptions options = new ListTransactionsOptions(); + ListTransactionsHandler handler = new ListTransactionsHandler(options, logContext); + ListTransactionsRequest request = handler.buildRequest(brokerId, singleton(brokerKey)).build(); + assertEquals(Collections.emptyList(), request.data().producerIdFilters()); + assertEquals(Collections.emptyList(), request.data().stateFilters()); + } + + @Test + public void testBuildRequestWithFilteredProducerId() { + int brokerId = 1; + BrokerKey brokerKey = new BrokerKey(OptionalInt.of(brokerId)); + long filteredProducerId = 23423L; + ListTransactionsOptions options = new ListTransactionsOptions() + .filterProducerIds(singleton(filteredProducerId)); + ListTransactionsHandler handler = new ListTransactionsHandler(options, logContext); + ListTransactionsRequest request = handler.buildRequest(brokerId, singleton(brokerKey)).build(); + assertEquals(Collections.singletonList(filteredProducerId), request.data().producerIdFilters()); + assertEquals(Collections.emptyList(), request.data().stateFilters()); + } + + @Test + public void testBuildRequestWithFilteredState() { + int brokerId = 1; + BrokerKey brokerKey = new BrokerKey(OptionalInt.of(brokerId)); + TransactionState filteredState = TransactionState.ONGOING; + ListTransactionsOptions options = new ListTransactionsOptions() + .filterStates(singleton(filteredState)); + ListTransactionsHandler handler = new ListTransactionsHandler(options, logContext); + ListTransactionsRequest request = handler.buildRequest(brokerId, singleton(brokerKey)).build(); + assertEquals(Collections.singletonList(filteredState.toString()), request.data().stateFilters()); + assertEquals(Collections.emptyList(), request.data().producerIdFilters()); + } + + @Test + public void testHandleSuccessfulResponse() { + int brokerId = 1; + BrokerKey brokerKey = new BrokerKey(OptionalInt.of(brokerId)); + ListTransactionsOptions options = new ListTransactionsOptions(); + ListTransactionsHandler handler = new ListTransactionsHandler(options, logContext); + ListTransactionsResponse response = sampleListTransactionsResponse1(); + ApiResult> result = handler.handleResponse( + brokerId, singleton(brokerKey), response); + assertEquals(singleton(brokerKey), result.completedKeys.keySet()); + assertExpectedTransactions(response.data().transactionStates(), result.completedKeys.get(brokerKey)); + } + + @Test + public void testCoordinatorLoadingErrorIsRetriable() { + int brokerId = 1; + ApiResult> result = + handleResponseWithError(brokerId, Errors.COORDINATOR_LOAD_IN_PROGRESS); + assertEquals(Collections.emptyMap(), result.completedKeys); + assertEquals(Collections.emptyMap(), result.failedKeys); + assertEquals(Collections.emptyList(), result.unmappedKeys); + } + + @Test + public void testHandleResponseWithFatalErrors() { + assertFatalError(Errors.COORDINATOR_NOT_AVAILABLE); + assertFatalError(Errors.UNKNOWN_SERVER_ERROR); + } + + private void assertFatalError( + Errors error + ) { + int brokerId = 1; + BrokerKey brokerKey = new BrokerKey(OptionalInt.of(brokerId)); + ApiResult> result = handleResponseWithError(brokerId, error); + assertEquals(Collections.emptyMap(), result.completedKeys); + assertEquals(Collections.emptyList(), result.unmappedKeys); + assertEquals(Collections.singleton(brokerKey), result.failedKeys.keySet()); + Throwable throwable = result.failedKeys.get(brokerKey); + assertEquals(error, Errors.forException(throwable)); + } + + private ApiResult> handleResponseWithError( + int brokerId, + Errors error + ) { + BrokerKey brokerKey = new BrokerKey(OptionalInt.of(brokerId)); + ListTransactionsOptions options = new ListTransactionsOptions(); + ListTransactionsHandler handler = new ListTransactionsHandler(options, logContext); + + ListTransactionsResponse response = new ListTransactionsResponse( + new ListTransactionsResponseData().setErrorCode(error.code()) + ); + return handler.handleResponse(brokerId, singleton(brokerKey), response); + } + + private ListTransactionsResponse sampleListTransactionsResponse1() { + return new ListTransactionsResponse( + new ListTransactionsResponseData() + .setErrorCode(Errors.NONE.code()) + .setTransactionStates(asList( + new ListTransactionsResponseData.TransactionState() + .setTransactionalId("foo") + .setProducerId(12345L) + .setTransactionState("Ongoing"), + new ListTransactionsResponseData.TransactionState() + .setTransactionalId("bar") + .setProducerId(98765L) + .setTransactionState("PrepareAbort") + )) + ); + } + + private void assertExpectedTransactions( + List expected, + Collection actual + ) { + assertEquals(expected.size(), actual.size()); + + Map expectedMap = expected.stream().collect(Collectors.toMap( + ListTransactionsResponseData.TransactionState::transactionalId, + Function.identity() + )); + + for (TransactionListing actualListing : actual) { + ListTransactionsResponseData.TransactionState expectedState = + expectedMap.get(actualListing.transactionalId()); + assertNotNull(expectedState); + assertExpectedTransactionState(expectedState, actualListing); + } + } + + private void assertExpectedTransactionState( + ListTransactionsResponseData.TransactionState expected, + TransactionListing actual + ) { + assertEquals(expected.transactionalId(), actual.transactionalId()); + assertEquals(expected.producerId(), actual.producerId()); + assertEquals(expected.transactionState(), actual.state().toString()); + } + +} diff --git a/clients/src/test/java/org/apache/kafka/clients/consumer/StickyAssignorTest.java b/clients/src/test/java/org/apache/kafka/clients/consumer/StickyAssignorTest.java index edc522bb082ec..684a421be1807 100644 --- a/clients/src/test/java/org/apache/kafka/clients/consumer/StickyAssignorTest.java +++ b/clients/src/test/java/org/apache/kafka/clients/consumer/StickyAssignorTest.java @@ -37,6 +37,8 @@ import org.apache.kafka.common.protocol.types.Struct; import org.apache.kafka.common.utils.CollectionUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class StickyAssignorTest extends AbstractStickyAssignorTest { @@ -51,132 +53,147 @@ public Subscription buildSubscription(List topics, List serializeTopicPartitionAssignment(new MemberData(partitions, Optional.of(DEFAULT_GENERATION)))); } - @Test - public void testAssignmentWithMultipleGenerations1() { - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - String consumer3 = "consumer3"; + @ParameterizedTest(name = "testAssignmentWithMultipleGenerations1 with isAllSubscriptionsEqual: {0}") + @ValueSource(booleans = {true, false}) + public void testAssignmentWithMultipleGenerations1(boolean isAllSubscriptionsEqual) { + List allTopics = topics(topic, topic2); + List consumer2SubscribedTopics = isAllSubscriptionsEqual ? allTopics : topics(topic); Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic, 6); - subscriptions.put(consumer1, new Subscription(topics(topic))); - subscriptions.put(consumer2, new Subscription(topics(topic))); - subscriptions.put(consumer3, new Subscription(topics(topic))); + partitionsPerTopic.put(topic2, 6); + subscriptions.put(consumer1, new Subscription(allTopics)); + subscriptions.put(consumer2, new Subscription(consumer2SubscribedTopics)); + subscriptions.put(consumer3, new Subscription(allTopics)); Map> assignment = assignor.assign(partitionsPerTopic, subscriptions); List r1partitions1 = assignment.get(consumer1); List r1partitions2 = assignment.get(consumer2); List r1partitions3 = assignment.get(consumer3); - assertTrue(r1partitions1.size() == 2 && r1partitions2.size() == 2 && r1partitions3.size() == 2); + assertTrue(r1partitions1.size() == 4 && r1partitions2.size() == 4 && r1partitions3.size() == 4); verifyValidityAndBalance(subscriptions, assignment, partitionsPerTopic); assertTrue(isFullyBalanced(assignment)); - subscriptions.put(consumer1, buildSubscription(topics(topic), r1partitions1)); - subscriptions.put(consumer2, buildSubscription(topics(topic), r1partitions2)); + subscriptions.put(consumer1, buildSubscription(allTopics, r1partitions1)); + subscriptions.put(consumer2, buildSubscription(consumer2SubscribedTopics, r1partitions2)); subscriptions.remove(consumer3); assignment = assignor.assign(partitionsPerTopic, subscriptions); List r2partitions1 = assignment.get(consumer1); List r2partitions2 = assignment.get(consumer2); - assertTrue(r2partitions1.size() == 3 && r2partitions2.size() == 3); - assertTrue(r2partitions1.containsAll(r1partitions1)); + assertTrue(r2partitions1.size() == 6 && r2partitions2.size() == 6); + if (isAllSubscriptionsEqual) { + // only true in all subscription equal case + assertTrue(r2partitions1.containsAll(r1partitions1)); + } assertTrue(r2partitions2.containsAll(r1partitions2)); verifyValidityAndBalance(subscriptions, assignment, partitionsPerTopic); assertTrue(isFullyBalanced(assignment)); assertFalse(Collections.disjoint(r2partitions2, r1partitions3)); subscriptions.remove(consumer1); - subscriptions.put(consumer2, buildSubscriptionWithGeneration(topics(topic), r2partitions2, 2)); - subscriptions.put(consumer3, buildSubscriptionWithGeneration(topics(topic), r1partitions3, 1)); + subscriptions.put(consumer2, buildSubscriptionWithGeneration(consumer2SubscribedTopics, r2partitions2, 2)); + subscriptions.put(consumer3, buildSubscriptionWithGeneration(allTopics, r1partitions3, 1)); assignment = assignor.assign(partitionsPerTopic, subscriptions); List r3partitions2 = assignment.get(consumer2); List r3partitions3 = assignment.get(consumer3); - assertTrue(r3partitions2.size() == 3 && r3partitions3.size() == 3); + assertTrue(r3partitions2.size() == 6 && r3partitions3.size() == 6); assertTrue(Collections.disjoint(r3partitions2, r3partitions3)); verifyValidityAndBalance(subscriptions, assignment, partitionsPerTopic); assertTrue(isFullyBalanced(assignment)); } - @Test - public void testAssignmentWithMultipleGenerations2() { - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - String consumer3 = "consumer3"; + @ParameterizedTest(name = "testAssignmentWithMultipleGenerations2 with isAllSubscriptionsEqual: {0}") + @ValueSource(booleans = {true, false}) + public void testAssignmentWithMultipleGenerations2(boolean isAllSubscriptionsEqual) { + List allTopics = topics(topic, topic2, topic3); + List consumer1SubscribedTopics = isAllSubscriptionsEqual ? allTopics : topics(topic); + List consumer3SubscribedTopics = isAllSubscriptionsEqual ? allTopics : topics(topic, topic2); Map partitionsPerTopic = new HashMap<>(); - partitionsPerTopic.put(topic, 6); - subscriptions.put(consumer1, new Subscription(topics(topic))); - subscriptions.put(consumer2, new Subscription(topics(topic))); - subscriptions.put(consumer3, new Subscription(topics(topic))); + partitionsPerTopic.put(topic, 4); + partitionsPerTopic.put(topic2, 4); + partitionsPerTopic.put(topic3, 4); + subscriptions.put(consumer1, new Subscription(consumer1SubscribedTopics)); + subscriptions.put(consumer2, new Subscription(allTopics)); + subscriptions.put(consumer3, new Subscription(consumer3SubscribedTopics)); Map> assignment = assignor.assign(partitionsPerTopic, subscriptions); List r1partitions1 = assignment.get(consumer1); List r1partitions2 = assignment.get(consumer2); List r1partitions3 = assignment.get(consumer3); - assertTrue(r1partitions1.size() == 2 && r1partitions2.size() == 2 && r1partitions3.size() == 2); + assertTrue(r1partitions1.size() == 4 && r1partitions2.size() == 4 && r1partitions3.size() == 4); verifyValidityAndBalance(subscriptions, assignment, partitionsPerTopic); assertTrue(isFullyBalanced(assignment)); subscriptions.remove(consumer1); - subscriptions.put(consumer2, buildSubscriptionWithGeneration(topics(topic), r1partitions2, 1)); + subscriptions.put(consumer2, buildSubscriptionWithGeneration(allTopics, r1partitions2, 1)); subscriptions.remove(consumer3); assignment = assignor.assign(partitionsPerTopic, subscriptions); List r2partitions2 = assignment.get(consumer2); - assertEquals(6, r2partitions2.size()); + assertEquals(12, r2partitions2.size()); assertTrue(r2partitions2.containsAll(r1partitions2)); verifyValidityAndBalance(subscriptions, assignment, partitionsPerTopic); assertTrue(isFullyBalanced(assignment)); - subscriptions.put(consumer1, buildSubscriptionWithGeneration(topics(topic), r1partitions1, 1)); - subscriptions.put(consumer2, buildSubscriptionWithGeneration(topics(topic), r2partitions2, 2)); - subscriptions.put(consumer3, buildSubscriptionWithGeneration(topics(topic), r1partitions3, 1)); + subscriptions.put(consumer1, buildSubscriptionWithGeneration(consumer1SubscribedTopics, r1partitions1, 1)); + subscriptions.put(consumer2, buildSubscriptionWithGeneration(allTopics, r2partitions2, 2)); + subscriptions.put(consumer3, buildSubscriptionWithGeneration(consumer3SubscribedTopics, r1partitions3, 1)); assignment = assignor.assign(partitionsPerTopic, subscriptions); List r3partitions1 = assignment.get(consumer1); List r3partitions2 = assignment.get(consumer2); List r3partitions3 = assignment.get(consumer3); - assertTrue(r3partitions1.size() == 2 && r3partitions2.size() == 2 && r3partitions3.size() == 2); - assertEquals(r1partitions1, r3partitions1); - assertEquals(r1partitions2, r3partitions2); - assertEquals(r1partitions3, r3partitions3); + assertTrue(r3partitions1.size() == 4 && r3partitions2.size() == 4 && r3partitions3.size() == 4); verifyValidityAndBalance(subscriptions, assignment, partitionsPerTopic); assertTrue(isFullyBalanced(assignment)); } - @Test - public void testAssignmentWithConflictingPreviousGenerations() { - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - String consumer3 = "consumer3"; - + @ParameterizedTest(name = "testAssignmentWithConflictingPreviousGenerations with isAllSubscriptionsEqual: {0}") + @ValueSource(booleans = {true, false}) + public void testAssignmentWithConflictingPreviousGenerations(boolean isAllSubscriptionsEqual) { Map partitionsPerTopic = new HashMap<>(); - partitionsPerTopic.put(topic, 6); - subscriptions.put(consumer1, new Subscription(topics(topic))); - subscriptions.put(consumer2, new Subscription(topics(topic))); - subscriptions.put(consumer3, new Subscription(topics(topic))); + partitionsPerTopic.put(topic, 4); + partitionsPerTopic.put(topic2, 4); + partitionsPerTopic.put(topic3, 4); + + List allTopics = topics(topic, topic2, topic3); + List consumer1SubscribedTopics = isAllSubscriptionsEqual ? allTopics : topics(topic); + List consumer2SubscribedTopics = isAllSubscriptionsEqual ? allTopics : topics(topic, topic2); + + subscriptions.put(consumer1, new Subscription(consumer1SubscribedTopics)); + subscriptions.put(consumer2, new Subscription(consumer2SubscribedTopics)); + subscriptions.put(consumer3, new Subscription(allTopics)); TopicPartition tp0 = new TopicPartition(topic, 0); TopicPartition tp1 = new TopicPartition(topic, 1); TopicPartition tp2 = new TopicPartition(topic, 2); TopicPartition tp3 = new TopicPartition(topic, 3); - TopicPartition tp4 = new TopicPartition(topic, 4); - TopicPartition tp5 = new TopicPartition(topic, 5); - - List c1partitions0 = partitions(tp0, tp1, tp4); - List c2partitions0 = partitions(tp0, tp1, tp2); - List c3partitions0 = partitions(tp3, tp4, tp5); - subscriptions.put(consumer1, buildSubscriptionWithGeneration(topics(topic), c1partitions0, 1)); - subscriptions.put(consumer2, buildSubscriptionWithGeneration(topics(topic), c2partitions0, 2)); - subscriptions.put(consumer3, buildSubscriptionWithGeneration(topics(topic), c3partitions0, 2)); + TopicPartition t2p0 = new TopicPartition(topic2, 0); + TopicPartition t2p1 = new TopicPartition(topic2, 1); + TopicPartition t2p2 = new TopicPartition(topic2, 2); + TopicPartition t2p3 = new TopicPartition(topic2, 3); + TopicPartition t3p0 = new TopicPartition(topic3, 0); + TopicPartition t3p1 = new TopicPartition(topic3, 1); + TopicPartition t3p2 = new TopicPartition(topic3, 2); + TopicPartition t3p3 = new TopicPartition(topic3, 3); + + List c1partitions0 = isAllSubscriptionsEqual ? partitions(tp0, tp1, tp2, t2p2, t2p3, t3p0) : + partitions(tp0, tp1, tp2, tp3); + List c2partitions0 = partitions(tp0, tp1, t2p0, t2p1, t2p2, t2p3); + List c3partitions0 = partitions(tp2, tp3, t3p0, t3p1, t3p2, t3p3); + subscriptions.put(consumer1, buildSubscriptionWithGeneration(consumer1SubscribedTopics, c1partitions0, 1)); + subscriptions.put(consumer2, buildSubscriptionWithGeneration(consumer2SubscribedTopics, c2partitions0, 2)); + subscriptions.put(consumer3, buildSubscriptionWithGeneration(allTopics, c3partitions0, 2)); Map> assignment = assignor.assign(partitionsPerTopic, subscriptions); List c1partitions = assignment.get(consumer1); List c2partitions = assignment.get(consumer2); List c3partitions = assignment.get(consumer3); - assertTrue(c1partitions.size() == 2 && c2partitions.size() == 2 && c3partitions.size() == 2); + assertTrue(c1partitions.size() == 4 && c2partitions.size() == 4 && c3partitions.size() == 4); assertTrue(c2partitions0.containsAll(c2partitions)); assertTrue(c3partitions0.containsAll(c3partitions)); verifyValidityAndBalance(subscriptions, assignment, partitionsPerTopic); @@ -185,10 +202,6 @@ public void testAssignmentWithConflictingPreviousGenerations() { @Test public void testSchemaBackwardCompatibility() { - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - String consumer3 = "consumer3"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic, 3); subscriptions.put(consumer1, new Subscription(topics(topic))); diff --git a/clients/src/test/java/org/apache/kafka/clients/consumer/internals/AbstractStickyAssignorTest.java b/clients/src/test/java/org/apache/kafka/clients/consumer/internals/AbstractStickyAssignorTest.java index c8f6c14c5e82a..a650cbbf39a67 100644 --- a/clients/src/test/java/org/apache/kafka/clients/consumer/internals/AbstractStickyAssignorTest.java +++ b/clients/src/test/java/org/apache/kafka/clients/consumer/internals/AbstractStickyAssignorTest.java @@ -43,8 +43,15 @@ public abstract class AbstractStickyAssignorTest { protected AbstractStickyAssignor assignor; protected String consumerId = "consumer"; + protected String consumer1 = "consumer1"; + protected String consumer2 = "consumer2"; + protected String consumer3 = "consumer3"; + protected String consumer4 = "consumer4"; protected Map subscriptions; protected String topic = "topic"; + protected String topic1 = "topic1"; + protected String topic2 = "topic2"; + protected String topic3 = "topic3"; protected abstract AbstractStickyAssignor createAssignor(); @@ -124,9 +131,6 @@ public void testOnlyAssignsPartitionsFromSubscribedTopics() { @Test public void testOneConsumerMultipleTopics() { - String topic1 = "topic1"; - String topic2 = "topic2"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic1, 1); partitionsPerTopic.put(topic2, 2); @@ -141,9 +145,6 @@ public void testOneConsumerMultipleTopics() { @Test public void testTwoConsumersOneTopicOnePartition() { - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic, 1); @@ -158,9 +159,6 @@ public void testTwoConsumersOneTopicOnePartition() { @Test public void testTwoConsumersOneTopicTwoPartitions() { - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic, 2); @@ -177,12 +175,6 @@ public void testTwoConsumersOneTopicTwoPartitions() { @Test public void testMultipleConsumersMixedTopicSubscriptions() { - String topic1 = "topic1"; - String topic2 = "topic2"; - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - String consumer3 = "consumer3"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic1, 3); partitionsPerTopic.put(topic2, 2); @@ -202,11 +194,6 @@ public void testMultipleConsumersMixedTopicSubscriptions() { @Test public void testTwoConsumersTwoTopicsSixPartitions() { - String topic1 = "topic1"; - String topic2 = "topic2"; - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic1, 3); partitionsPerTopic.put(topic2, 3); @@ -227,11 +214,6 @@ public void testTwoConsumersTwoTopicsSixPartitions() { */ @Test public void testConsumerOwningMinQuotaExpectedMaxQuota() { - String topic1 = "topic1"; - String topic2 = "topic2"; - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic1, 2); partitionsPerTopic.put(topic2, 3); @@ -256,12 +238,6 @@ public void testConsumerOwningMinQuotaExpectedMaxQuota() { */ @Test public void testMaxQuotaConsumerMoreThanNumExpectedMaxCapacityMembers() { - String topic1 = "topic1"; - String topic2 = "topic2"; - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - String consumer3 = "consumer3"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic1, 2); partitionsPerTopic.put(topic2, 2); @@ -289,12 +265,6 @@ public void testMaxQuotaConsumerMoreThanNumExpectedMaxCapacityMembers() { */ @Test public void testAllConsumerAreUnderMinQuota() { - String topic1 = "topic1"; - String topic2 = "topic2"; - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - String consumer3 = "consumer3"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic1, 2); partitionsPerTopic.put(topic2, 3); @@ -319,8 +289,6 @@ public void testAllConsumerAreUnderMinQuota() { @Test public void testAddRemoveConsumerOneTopic() { - String consumer1 = "consumer1"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic, 3); subscriptions.put(consumer1, new Subscription(topics(topic))); @@ -331,7 +299,6 @@ public void testAddRemoveConsumerOneTopic() { verifyValidityAndBalance(subscriptions, assignment, partitionsPerTopic); assertTrue(isFullyBalanced(assignment)); - String consumer2 = "consumer2"; subscriptions.put(consumer1, buildSubscription(topics(topic), assignment.get(consumer1))); subscriptions.put(consumer2, buildSubscription(topics(topic), Collections.emptyList())); assignment = assignor.assign(partitionsPerTopic, subscriptions); @@ -353,12 +320,6 @@ public void testAddRemoveConsumerOneTopic() { @Test public void testAddRemoveTwoConsumersTwoTopics() { - String topic1 = "topic1"; - String topic2 = "topic2"; - String consumer1 = "consumer1"; - String consumer2 = "consumer2"; - String consumer3 = "consumer3"; - String consumer4 = "consumer4"; List allTopics = topics(topic1, topic2); Map partitionsPerTopic = new HashMap<>(); @@ -441,9 +402,6 @@ public void testPoorRoundRobinAssignmentScenario() { @Test public void testAddRemoveTopicTwoConsumers() { - String consumer1 = "consumer"; - String consumer2 = "consumer2"; - Map partitionsPerTopic = new HashMap<>(); partitionsPerTopic.put(topic, 3); subscriptions.put(consumer1, new Subscription(topics(topic))); @@ -459,7 +417,6 @@ public void testAddRemoveTopicTwoConsumers() { assertTrue((consumer1Assignment1.size() == 1 && consumer2Assignment1.size() == 2) || (consumer1Assignment1.size() == 2 && consumer2Assignment1.size() == 1)); - String topic2 = "topic2"; partitionsPerTopic.put(topic2, 3); subscriptions.put(consumer1, buildSubscription(topics(topic, topic2), assignment.get(consumer1))); subscriptions.put(consumer2, buildSubscription(topics(topic, topic2), assignment.get(consumer2))); @@ -598,6 +555,43 @@ public void testLargeAssignmentAndGroupWithUniformSubscription() { assignor.assign(partitionsPerTopic, subscriptions); } + @Timeout(40) + @Test + public void testLargeAssignmentAndGroupWithNonEqualSubscription() { + // 1 million partitions! + int topicCount = 500; + int partitionCount = 2_000; + int consumerCount = 2_000; + + List topics = new ArrayList<>(); + Map partitionsPerTopic = new HashMap<>(); + for (int i = 0; i < topicCount; i++) { + String topicName = getTopicName(i, topicCount); + topics.add(topicName); + partitionsPerTopic.put(topicName, partitionCount); + } + for (int i = 0; i < consumerCount; i++) { + if (i == consumerCount - 1) { + subscriptions.put(getConsumerName(i, consumerCount), new Subscription(topics.subList(0, 1))); + } else { + subscriptions.put(getConsumerName(i, consumerCount), new Subscription(topics)); + } + } + + Map> assignment = assignor.assign(partitionsPerTopic, subscriptions); + + for (int i = 1; i < consumerCount; i++) { + String consumer = getConsumerName(i, consumerCount); + if (i == consumerCount - 1) { + subscriptions.put(consumer, buildSubscription(topics.subList(0, 1), assignment.get(consumer))); + } else { + subscriptions.put(consumer, buildSubscription(topics, assignment.get(consumer))); + } + } + + assignor.assign(partitionsPerTopic, subscriptions); + } + @Test public void testLargeAssignmentWithMultipleConsumersLeavingAndRandomSubscription() { Random rand = new Random(); @@ -658,19 +652,23 @@ public void testNewSubscription() { @Test public void testMoveExistingAssignments() { + String topic4 = "topic4"; + String topic5 = "topic5"; + String topic6 = "topic6"; + Map partitionsPerTopic = new HashMap<>(); for (int i = 1; i <= 6; i++) - partitionsPerTopic.put(String.format("topic%02d", i), 1); - - subscriptions.put("consumer01", - buildSubscription(topics("topic01", "topic02"), - partitions(tp("topic01", 0)))); - subscriptions.put("consumer02", - buildSubscription(topics("topic01", "topic02", "topic03", "topic04"), - partitions(tp("topic02", 0), tp("topic03", 0)))); - subscriptions.put("consumer03", - buildSubscription(topics("topic02", "topic03", "topic04", "topic05", "topic06"), - partitions(tp("topic04", 0), tp("topic05", 0), tp("topic06", 0)))); + partitionsPerTopic.put(String.format("topic%d", i), 1); + + subscriptions.put(consumer1, + buildSubscription(topics(topic1, topic2), + partitions(tp(topic1, 0)))); + subscriptions.put(consumer2, + buildSubscription(topics(topic1, topic2, topic3, topic4), + partitions(tp(topic2, 0), tp(topic3, 0)))); + subscriptions.put(consumer3, + buildSubscription(topics(topic2, topic3, topic4, topic5, topic6), + partitions(tp(topic4, 0), tp(topic5, 0), tp(topic6, 0)))); Map> assignment = assignor.assign(partitionsPerTopic, subscriptions); verifyValidityAndBalance(subscriptions, assignment, partitionsPerTopic); @@ -679,16 +677,12 @@ public void testMoveExistingAssignments() { @Test public void testStickiness() { Map partitionsPerTopic = new HashMap<>(); - partitionsPerTopic.put("topic01", 3); - String consumer1 = "consumer01"; - String consumer2 = "consumer02"; - String consumer3 = "consumer03"; - String consumer4 = "consumer04"; + partitionsPerTopic.put(topic1, 3); - subscriptions.put(consumer1, new Subscription(topics("topic01"))); - subscriptions.put(consumer2, new Subscription(topics("topic01"))); - subscriptions.put(consumer3, new Subscription(topics("topic01"))); - subscriptions.put(consumer4, new Subscription(topics("topic01"))); + subscriptions.put(consumer1, new Subscription(topics(topic1))); + subscriptions.put(consumer2, new Subscription(topics(topic1))); + subscriptions.put(consumer3, new Subscription(topics(topic1))); + subscriptions.put(consumer4, new Subscription(topics(topic1))); Map> assignment = assignor.assign(partitionsPerTopic, subscriptions); verifyValidityAndBalance(subscriptions, assignment, partitionsPerTopic); @@ -707,11 +701,11 @@ public void testStickiness() { // removing the potential group leader subscriptions.remove(consumer1); subscriptions.put(consumer2, - buildSubscription(topics("topic01"), assignment.get(consumer2))); + buildSubscription(topics(topic1), assignment.get(consumer2))); subscriptions.put(consumer3, - buildSubscription(topics("topic01"), assignment.get(consumer3))); + buildSubscription(topics(topic1), assignment.get(consumer3))); subscriptions.put(consumer4, - buildSubscription(topics("topic01"), assignment.get(consumer4))); + buildSubscription(topics(topic1), assignment.get(consumer4))); assignment = assignor.assign(partitionsPerTopic, subscriptions); @@ -730,9 +724,9 @@ public void testStickiness() { @Test public void testAssignmentUpdatedForDeletedTopic() { Map partitionsPerTopic = new HashMap<>(); - partitionsPerTopic.put("topic01", 1); - partitionsPerTopic.put("topic03", 100); - subscriptions = Collections.singletonMap(consumerId, new Subscription(topics("topic01", "topic02", "topic03"))); + partitionsPerTopic.put(topic1, 1); + partitionsPerTopic.put(topic3, 100); + subscriptions = Collections.singletonMap(consumerId, new Subscription(topics(topic1, topic2, topic3))); Map> assignment = assignor.assign(partitionsPerTopic, subscriptions); assertEquals(assignment.values().stream().mapToInt(List::size).sum(), 1 + 100); @@ -764,12 +758,12 @@ public void testReassignmentWithRandomSubscriptionsAndChanges() { int numTopics = minNumTopics + new Random().nextInt(maxNumTopics - minNumTopics); ArrayList topics = new ArrayList<>(); - for (int i = 0; i < numTopics; ++i) - topics.add(getTopicName(i, maxNumTopics)); Map partitionsPerTopic = new HashMap<>(); - for (int i = 0; i < numTopics; ++i) + for (int i = 0; i < numTopics; ++i) { + topics.add(getTopicName(i, maxNumTopics)); partitionsPerTopic.put(getTopicName(i, maxNumTopics), i + 1); + } int numConsumers = minNumConsumers + new Random().nextInt(maxNumConsumers - minNumConsumers); diff --git a/clients/src/test/java/org/apache/kafka/test/TestUtils.java b/clients/src/test/java/org/apache/kafka/test/TestUtils.java index 5e34ae8c5b369..3c819befa5fa8 100644 --- a/clients/src/test/java/org/apache/kafka/test/TestUtils.java +++ b/clients/src/test/java/org/apache/kafka/test/TestUtils.java @@ -298,7 +298,7 @@ public static void waitForCondition(final TestCondition testCondition, final lon * avoid transient failures due to slow or overloaded machines. */ public static void waitForCondition(final TestCondition testCondition, final long maxWaitMs, Supplier conditionDetailsSupplier) throws InterruptedException { - waitForCondition(testCondition, DEFAULT_POLL_INTERVAL_MS, maxWaitMs, conditionDetailsSupplier); + waitForCondition(testCondition, maxWaitMs, DEFAULT_POLL_INTERVAL_MS, conditionDetailsSupplier); } /** @@ -310,11 +310,11 @@ public static void waitForCondition(final TestCondition testCondition, final lon */ public static void waitForCondition( final TestCondition testCondition, - final long pollIntervalMs, final long maxWaitMs, + final long pollIntervalMs, Supplier conditionDetailsSupplier ) throws InterruptedException { - retryOnExceptionWithTimeout(pollIntervalMs, maxWaitMs, () -> { + retryOnExceptionWithTimeout(maxWaitMs, pollIntervalMs, () -> { String conditionDetailsSupplied = conditionDetailsSupplier != null ? conditionDetailsSupplier.get() : null; String conditionDetails = conditionDetailsSupplied != null ? conditionDetailsSupplied : ""; assertTrue(testCondition.conditionMet(), @@ -333,7 +333,7 @@ public static void waitForCondition( */ public static void retryOnExceptionWithTimeout(final long timeoutMs, final ValuelessCallable runnable) throws InterruptedException { - retryOnExceptionWithTimeout(DEFAULT_POLL_INTERVAL_MS, timeoutMs, runnable); + retryOnExceptionWithTimeout(timeoutMs, DEFAULT_POLL_INTERVAL_MS, runnable); } /** @@ -345,7 +345,7 @@ public static void retryOnExceptionWithTimeout(final long timeoutMs, * @throws InterruptedException if the current thread is interrupted while waiting for {@code runnable} to complete successfully. */ public static void retryOnExceptionWithTimeout(final ValuelessCallable runnable) throws InterruptedException { - retryOnExceptionWithTimeout(DEFAULT_POLL_INTERVAL_MS, DEFAULT_MAX_WAIT_MS, runnable); + retryOnExceptionWithTimeout(DEFAULT_MAX_WAIT_MS, DEFAULT_POLL_INTERVAL_MS, runnable); } /** @@ -353,13 +353,13 @@ public static void retryOnExceptionWithTimeout(final ValuelessCallable runnable) * {@link AssertionError}s, or for the given timeout to expire. If the timeout expires then the * last exception or assertion failure will be thrown thus providing context for the failure. * - * @param pollIntervalMs the interval in milliseconds to wait between invoking {@code runnable}. * @param timeoutMs the total time in milliseconds to wait for {@code runnable} to complete successfully. + * @param pollIntervalMs the interval in milliseconds to wait between invoking {@code runnable}. * @param runnable the code to attempt to execute successfully. * @throws InterruptedException if the current thread is interrupted while waiting for {@code runnable} to complete successfully. */ - public static void retryOnExceptionWithTimeout(final long pollIntervalMs, - final long timeoutMs, + public static void retryOnExceptionWithTimeout(final long timeoutMs, + final long pollIntervalMs, final ValuelessCallable runnable) throws InterruptedException { final long expectedEnd = System.currentTimeMillis() + timeoutMs; diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/CheckpointTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/CheckpointTest.java index fd5448c13f93a..f008f996772ce 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/CheckpointTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/CheckpointTest.java @@ -32,9 +32,13 @@ public void testSerde() { byte[] value = checkpoint.recordValue(); ConsumerRecord record = new ConsumerRecord<>("any-topic", 7, 8, key, value); Checkpoint deserialized = Checkpoint.deserializeRecord(record); - assertEquals(checkpoint.consumerGroupId(), deserialized.consumerGroupId()); - assertEquals(checkpoint.topicPartition(), deserialized.topicPartition()); - assertEquals(checkpoint.upstreamOffset(), deserialized.upstreamOffset()); - assertEquals(checkpoint.downstreamOffset(), deserialized.downstreamOffset()); + assertEquals(checkpoint.consumerGroupId(), deserialized.consumerGroupId(), + "Failure on checkpoint consumerGroupId serde"); + assertEquals(checkpoint.topicPartition(), deserialized.topicPartition(), + "Failure on checkpoint topicPartition serde"); + assertEquals(checkpoint.upstreamOffset(), deserialized.upstreamOffset(), + "Failure on checkpoint upstreamOffset serde"); + assertEquals(checkpoint.downstreamOffset(), deserialized.downstreamOffset(), + "Failure on checkpoint downstreamOffset serde"); } } diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/HeartbeatTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/HeartbeatTest.java index fb473f7b0af0c..723b0dc2bfe56 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/HeartbeatTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/HeartbeatTest.java @@ -31,8 +31,11 @@ public void testSerde() { byte[] value = heartbeat.recordValue(); ConsumerRecord record = new ConsumerRecord<>("any-topic", 6, 7, key, value); Heartbeat deserialized = Heartbeat.deserializeRecord(record); - assertEquals(heartbeat.sourceClusterAlias(), deserialized.sourceClusterAlias()); - assertEquals(heartbeat.targetClusterAlias(), deserialized.targetClusterAlias()); - assertEquals(heartbeat.timestamp(), deserialized.timestamp()); + assertEquals(heartbeat.sourceClusterAlias(), deserialized.sourceClusterAlias(), + "Failure on heartbeat sourceClusterAlias serde"); + assertEquals(heartbeat.targetClusterAlias(), deserialized.targetClusterAlias(), + "Failure on heartbeat targetClusterAlias serde"); + assertEquals(heartbeat.timestamp(), deserialized.timestamp(), + "Failure on heartbeat timestamp serde"); } } diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorCheckpointConnectorTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorCheckpointConnectorTest.java index 3c8453c77518a..1391e7615d3a0 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorCheckpointConnectorTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorCheckpointConnectorTest.java @@ -38,6 +38,8 @@ public class MirrorCheckpointConnectorTest { + private static final String CONSUMER_GROUP = "consumer-group-1"; + @Test public void testMirrorCheckpointConnectorDisabled() { // disable the checkpoint emission @@ -45,13 +47,13 @@ public void testMirrorCheckpointConnectorDisabled() { makeProps("emit.checkpoints.enabled", "false")); List knownConsumerGroups = new ArrayList<>(); - knownConsumerGroups.add("consumer-group-1"); + knownConsumerGroups.add(CONSUMER_GROUP); // MirrorCheckpointConnector as minimum to run taskConfig() MirrorCheckpointConnector connector = new MirrorCheckpointConnector(knownConsumerGroups, config); List> output = connector.taskConfigs(1); // expect no task will be created - assertEquals(0, output.size()); + assertEquals(0, output.size(), "MirrorCheckpointConnector not disabled"); } @Test @@ -61,14 +63,16 @@ public void testMirrorCheckpointConnectorEnabled() { makeProps("emit.checkpoints.enabled", "true")); List knownConsumerGroups = new ArrayList<>(); - knownConsumerGroups.add("consumer-group-1"); + knownConsumerGroups.add(CONSUMER_GROUP); // MirrorCheckpointConnector as minimum to run taskConfig() MirrorCheckpointConnector connector = new MirrorCheckpointConnector(knownConsumerGroups, config); List> output = connector.taskConfigs(1); // expect 1 task will be created - assertEquals(1, output.size()); - assertEquals("consumer-group-1", output.get(0).get(MirrorConnectorConfig.TASK_CONSUMER_GROUPS)); + assertEquals(1, output.size(), + "MirrorCheckpointConnectorEnabled for " + CONSUMER_GROUP + " has incorrect size"); + assertEquals(CONSUMER_GROUP, output.get(0).get(MirrorConnectorConfig.TASK_CONSUMER_GROUPS), + "MirrorCheckpointConnectorEnabled for " + CONSUMER_GROUP + " failed"); } @Test @@ -77,7 +81,7 @@ public void testNoConsumerGroup() { MirrorCheckpointConnector connector = new MirrorCheckpointConnector(new ArrayList<>(), config); List> output = connector.taskConfigs(1); // expect no task will be created - assertEquals(0, output.size()); + assertEquals(0, output.size(), "ConsumerGroup shouldn't exist"); } @Test @@ -86,12 +90,12 @@ public void testReplicationDisabled() { MirrorConnectorConfig config = new MirrorConnectorConfig(makeProps("enabled", "false")); List knownConsumerGroups = new ArrayList<>(); - knownConsumerGroups.add("consumer-group-1"); + knownConsumerGroups.add(CONSUMER_GROUP); // MirrorCheckpointConnector as minimum to run taskConfig() MirrorCheckpointConnector connector = new MirrorCheckpointConnector(knownConsumerGroups, config); List> output = connector.taskConfigs(1); // expect no task will be created - assertEquals(0, output.size()); + assertEquals(0, output.size(), "Replication isn't disabled"); } @Test @@ -100,13 +104,14 @@ public void testReplicationEnabled() { MirrorConnectorConfig config = new MirrorConnectorConfig(makeProps("enabled", "true")); List knownConsumerGroups = new ArrayList<>(); - knownConsumerGroups.add("consumer-group-1"); + knownConsumerGroups.add(CONSUMER_GROUP); // MirrorCheckpointConnector as minimum to run taskConfig() MirrorCheckpointConnector connector = new MirrorCheckpointConnector(knownConsumerGroups, config); List> output = connector.taskConfigs(1); // expect 1 task will be created - assertEquals(1, output.size()); - assertEquals("consumer-group-1", output.get(0).get(MirrorConnectorConfig.TASK_CONSUMER_GROUPS)); + assertEquals(1, output.size(), "Replication for consumer-group-1 has incorrect size"); + assertEquals(CONSUMER_GROUP, output.get(0).get(MirrorConnectorConfig.TASK_CONSUMER_GROUPS), + "Replication for consumer-group-1 failed"); } @Test @@ -123,7 +128,8 @@ public void testFindConsumerGroups() throws Exception { List groupFound = connector.findConsumerGroups(); Set expectedGroups = groups.stream().map(ConsumerGroupListing::groupId).collect(Collectors.toSet()); - assertEquals(expectedGroups, new HashSet<>(groupFound)); + assertEquals(expectedGroups, new HashSet<>(groupFound), + "Expected groups are not the same as findConsumerGroups"); } } diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorCheckpointTaskTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorCheckpointTaskTest.java index abd314bb71c9e..7ef878ab2e8d3 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorCheckpointTaskTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorCheckpointTaskTest.java @@ -36,11 +36,14 @@ public void testDownstreamTopicRenaming() { MirrorCheckpointTask mirrorCheckpointTask = new MirrorCheckpointTask("source1", "target2", new DefaultReplicationPolicy(), null, Collections.emptyMap(), Collections.emptyMap()); assertEquals(new TopicPartition("source1.topic3", 4), - mirrorCheckpointTask.renameTopicPartition(new TopicPartition("topic3", 4))); + mirrorCheckpointTask.renameTopicPartition(new TopicPartition("topic3", 4)), + "Renaming source1.topic3 failed"); assertEquals(new TopicPartition("topic3", 5), - mirrorCheckpointTask.renameTopicPartition(new TopicPartition("target2.topic3", 5))); + mirrorCheckpointTask.renameTopicPartition(new TopicPartition("target2.topic3", 5)), + "Renaming target2.topic3 failed"); assertEquals(new TopicPartition("source1.source6.topic7", 8), - mirrorCheckpointTask.renameTopicPartition(new TopicPartition("source6.topic7", 8))); + mirrorCheckpointTask.renameTopicPartition(new TopicPartition("source6.topic7", 8)), + "Renaming source1.source6.topic7 failed"); } @Test @@ -53,21 +56,33 @@ public void testCheckpoint() { Checkpoint checkpoint1 = mirrorCheckpointTask.checkpoint("group9", new TopicPartition("topic1", 2), new OffsetAndMetadata(10, null)); SourceRecord sourceRecord1 = mirrorCheckpointTask.checkpointRecord(checkpoint1, 123L); - assertEquals(new TopicPartition("source1.topic1", 2), checkpoint1.topicPartition()); - assertEquals("group9", checkpoint1.consumerGroupId()); - assertEquals("group9", Checkpoint.unwrapGroup(sourceRecord1.sourcePartition())); - assertEquals(10, checkpoint1.upstreamOffset()); - assertEquals(11, checkpoint1.downstreamOffset()); - assertEquals(123L, sourceRecord1.timestamp().longValue()); + assertEquals(new TopicPartition("source1.topic1", 2), checkpoint1.topicPartition(), + "checkpoint group9 source1.topic1 failed"); + assertEquals("group9", checkpoint1.consumerGroupId(), + "checkpoint group9 consumerGroupId failed"); + assertEquals("group9", Checkpoint.unwrapGroup(sourceRecord1.sourcePartition()), + "checkpoint group9 sourcePartition failed"); + assertEquals(10, checkpoint1.upstreamOffset(), + "checkpoint group9 upstreamOffset failed"); + assertEquals(11, checkpoint1.downstreamOffset(), + "checkpoint group9 downstreamOffset failed"); + assertEquals(123L, sourceRecord1.timestamp().longValue(), + "checkpoint group9 timestamp failed"); Checkpoint checkpoint2 = mirrorCheckpointTask.checkpoint("group11", new TopicPartition("target2.topic5", 6), new OffsetAndMetadata(12, null)); SourceRecord sourceRecord2 = mirrorCheckpointTask.checkpointRecord(checkpoint2, 234L); - assertEquals(new TopicPartition("topic5", 6), checkpoint2.topicPartition()); - assertEquals("group11", checkpoint2.consumerGroupId()); - assertEquals("group11", Checkpoint.unwrapGroup(sourceRecord2.sourcePartition())); - assertEquals(12, checkpoint2.upstreamOffset()); - assertEquals(13, checkpoint2.downstreamOffset()); - assertEquals(234L, sourceRecord2.timestamp().longValue()); + assertEquals(new TopicPartition("topic5", 6), checkpoint2.topicPartition(), + "checkpoint group11 topic5 failed"); + assertEquals("group11", checkpoint2.consumerGroupId(), + "checkpoint group11 consumerGroupId failed"); + assertEquals("group11", Checkpoint.unwrapGroup(sourceRecord2.sourcePartition()), + "checkpoint group11 sourcePartition failed"); + assertEquals(12, checkpoint2.upstreamOffset(), + "checkpoint group11 upstreamOffset failed"); + assertEquals(13, checkpoint2.downstreamOffset(), + "checkpoint group11 downstreamOffset failed"); + assertEquals(234L, sourceRecord2.timestamp().longValue(), + "checkpoint group11 timestamp failed"); } @Test @@ -118,7 +133,9 @@ public void testSyncOffset() { Map> output = mirrorCheckpointTask.syncGroupOffset(); - assertEquals(101, output.get(consumer1).get(t1p0).offset()); - assertEquals(51, output.get(consumer2).get(t2p0).offset()); + assertEquals(101, output.get(consumer1).get(t1p0).offset(), + "Consumer 1 " + topic1 + " failed"); + assertEquals(51, output.get(consumer2).get(t2p0).offset(), + "Consumer 2 " + topic2 + " failed"); } } diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorConnectorConfigTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorConnectorConfigTest.java index f53aa68387276..7abe30def6ddc 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorConnectorConfigTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorConnectorConfigTest.java @@ -41,7 +41,8 @@ public void testTaskConfigTopicPartitions() { MirrorConnectorConfig config = new MirrorConnectorConfig(makeProps()); Map props = config.taskConfigForTopicPartitions(topicPartitions); MirrorTaskConfig taskConfig = new MirrorTaskConfig(props); - assertEquals(taskConfig.taskTopicPartitions(), new HashSet<>(topicPartitions)); + assertEquals(taskConfig.taskTopicPartitions(), new HashSet<>(topicPartitions), + "Setting topic property configuration failed"); } @Test @@ -50,29 +51,36 @@ public void testTaskConfigConsumerGroups() { MirrorConnectorConfig config = new MirrorConnectorConfig(makeProps()); Map props = config.taskConfigForConsumerGroups(groups); MirrorTaskConfig taskConfig = new MirrorTaskConfig(props); - assertEquals(taskConfig.taskConsumerGroups(), new HashSet<>(groups)); + assertEquals(taskConfig.taskConsumerGroups(), new HashSet<>(groups), + "Setting consumer groups property configuration failed"); } @Test public void testTopicMatching() { MirrorConnectorConfig config = new MirrorConnectorConfig(makeProps("topics", "topic1")); - assertTrue(config.topicFilter().shouldReplicateTopic("topic1")); - assertFalse(config.topicFilter().shouldReplicateTopic("topic2")); + assertTrue(config.topicFilter().shouldReplicateTopic("topic1"), + "topic1 replication property configuration failed"); + assertFalse(config.topicFilter().shouldReplicateTopic("topic2"), + "topic2 replication property configuration failed"); } @Test public void testGroupMatching() { MirrorConnectorConfig config = new MirrorConnectorConfig(makeProps("groups", "group1")); - assertTrue(config.groupFilter().shouldReplicateGroup("group1")); - assertFalse(config.groupFilter().shouldReplicateGroup("group2")); + assertTrue(config.groupFilter().shouldReplicateGroup("group1"), + "topic1 group matching property configuration failed"); + assertFalse(config.groupFilter().shouldReplicateGroup("group2"), + "topic2 group matching property configuration failed"); } @Test public void testConfigPropertyMatching() { MirrorConnectorConfig config = new MirrorConnectorConfig( makeProps("config.properties.exclude", "prop2")); - assertTrue(config.configPropertyFilter().shouldReplicateConfigProperty("prop1")); - assertFalse(config.configPropertyFilter().shouldReplicateConfigProperty("prop2")); + assertTrue(config.configPropertyFilter().shouldReplicateConfigProperty("prop1"), + "config.properties.exclude incorrectly excluded prop1"); + assertFalse(config.configPropertyFilter().shouldReplicateConfigProperty("prop2"), + "config.properties.exclude incorrectly included prop2"); } @Test @@ -92,24 +100,26 @@ public void testConfigBackwardsCompatibility() { @Test public void testNoTopics() { MirrorConnectorConfig config = new MirrorConnectorConfig(makeProps("topics", "")); - assertFalse(config.topicFilter().shouldReplicateTopic("topic1")); - assertFalse(config.topicFilter().shouldReplicateTopic("topic2")); - assertFalse(config.topicFilter().shouldReplicateTopic("")); + assertFalse(config.topicFilter().shouldReplicateTopic("topic1"), "topic1 shouldn't exist"); + assertFalse(config.topicFilter().shouldReplicateTopic("topic2"), "topic2 shouldn't exist"); + assertFalse(config.topicFilter().shouldReplicateTopic(""), "Empty topic shouldn't exist"); } @Test public void testAllTopics() { MirrorConnectorConfig config = new MirrorConnectorConfig(makeProps("topics", ".*")); - assertTrue(config.topicFilter().shouldReplicateTopic("topic1")); - assertTrue(config.topicFilter().shouldReplicateTopic("topic2")); + assertTrue(config.topicFilter().shouldReplicateTopic("topic1"), + "topic1 created from wildcard should exist"); + assertTrue(config.topicFilter().shouldReplicateTopic("topic2"), + "topic2 created from wildcard should exist"); } @Test public void testListOfTopics() { MirrorConnectorConfig config = new MirrorConnectorConfig(makeProps("topics", "topic1, topic2")); - assertTrue(config.topicFilter().shouldReplicateTopic("topic1")); - assertTrue(config.topicFilter().shouldReplicateTopic("topic2")); - assertFalse(config.topicFilter().shouldReplicateTopic("topic3")); + assertTrue(config.topicFilter().shouldReplicateTopic("topic1"), "topic1 created from list should exist"); + assertTrue(config.topicFilter().shouldReplicateTopic("topic2"), "topic2 created from list should exist"); + assertFalse(config.topicFilter().shouldReplicateTopic("topic3"), "topic3 created from list should exist"); } @Test @@ -156,7 +166,8 @@ public void testSourceConsumerConfig() { connectorConsumerProps = config.sourceConsumerConfig(); expectedConsumerProps.put("auto.offset.reset", "latest"); expectedConsumerProps.remove("max.poll.interval.ms"); - assertEquals(expectedConsumerProps, connectorConsumerProps); + assertEquals(expectedConsumerProps, connectorConsumerProps, + MirrorConnectorConfig.CONSUMER_CLIENT_PREFIX + " source consumer config not matching"); } @Test @@ -172,7 +183,8 @@ public void testSourceConsumerConfigWithSourcePrefix() { expectedConsumerProps.put("enable.auto.commit", "false"); expectedConsumerProps.put("auto.offset.reset", "latest"); expectedConsumerProps.put("max.poll.interval.ms", "100"); - assertEquals(expectedConsumerProps, connectorConsumerProps); + assertEquals(expectedConsumerProps, connectorConsumerProps, + prefix + " source consumer config not matching"); } @Test @@ -184,7 +196,8 @@ public void testSourceProducerConfig() { Map connectorProducerProps = config.sourceProducerConfig(); Map expectedProducerProps = new HashMap<>(); expectedProducerProps.put("acks", "1"); - assertEquals(expectedProducerProps, connectorProducerProps); + assertEquals(expectedProducerProps, connectorProducerProps, + MirrorConnectorConfig.PRODUCER_CLIENT_PREFIX + " source product config not matching"); } @Test @@ -195,7 +208,8 @@ public void testSourceProducerConfigWithSourcePrefix() { Map connectorProducerProps = config.sourceProducerConfig(); Map expectedProducerProps = new HashMap<>(); expectedProducerProps.put("acks", "1"); - assertEquals(expectedProducerProps, connectorProducerProps); + assertEquals(expectedProducerProps, connectorProducerProps, + prefix + " source producer config not matching"); } @Test @@ -208,7 +222,8 @@ public void testSourceAdminConfig() { Map connectorAdminProps = config.sourceAdminConfig(); Map expectedAdminProps = new HashMap<>(); expectedAdminProps.put("connections.max.idle.ms", "10000"); - assertEquals(expectedAdminProps, connectorAdminProps); + assertEquals(expectedAdminProps, connectorAdminProps, + MirrorConnectorConfig.ADMIN_CLIENT_PREFIX + " source connector admin props not matching"); } @Test @@ -219,7 +234,7 @@ public void testSourceAdminConfigWithSourcePrefix() { Map connectorAdminProps = config.sourceAdminConfig(); Map expectedAdminProps = new HashMap<>(); expectedAdminProps.put("connections.max.idle.ms", "10000"); - assertEquals(expectedAdminProps, connectorAdminProps); + assertEquals(expectedAdminProps, connectorAdminProps, prefix + " source connector admin props not matching"); } @Test @@ -232,7 +247,8 @@ public void testTargetAdminConfig() { Map connectorAdminProps = config.targetAdminConfig(); Map expectedAdminProps = new HashMap<>(); expectedAdminProps.put("connections.max.idle.ms", "10000"); - assertEquals(expectedAdminProps, connectorAdminProps); + assertEquals(expectedAdminProps, connectorAdminProps, + MirrorConnectorConfig.ADMIN_CLIENT_PREFIX + " target connector admin props not matching"); } @Test @@ -243,7 +259,7 @@ public void testTargetAdminConfigWithSourcePrefix() { Map connectorAdminProps = config.targetAdminConfig(); Map expectedAdminProps = new HashMap<>(); expectedAdminProps.put("connections.max.idle.ms", "10000"); - assertEquals(expectedAdminProps, connectorAdminProps); + assertEquals(expectedAdminProps, connectorAdminProps, prefix + " source connector admin props not matching"); } } diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorHeartBeatConnectorTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorHeartBeatConnectorTest.java index b48c46977078e..ec0691983a63f 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorHeartBeatConnectorTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorHeartBeatConnectorTest.java @@ -34,7 +34,7 @@ public void testMirrorHeartbeatConnectorDisabled() { MirrorHeartbeatConnector connector = new MirrorHeartbeatConnector(config); List> output = connector.taskConfigs(1); // expect no task will be created - assertEquals(0, output.size()); + assertEquals(0, output.size(), "Expected task to not be created"); } @Test @@ -47,6 +47,6 @@ public void testReplicationDisabled() { MirrorHeartbeatConnector connector = new MirrorHeartbeatConnector(config); List> output = connector.taskConfigs(1); // expect one task will be created, even the replication is disabled - assertEquals(1, output.size()); + assertEquals(1, output.size(), "Task should have been created even with replication disabled"); } } diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorHeartbeatTaskTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorHeartbeatTaskTest.java index d4f96e785b9f4..39fd6dff10e30 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorHeartbeatTaskTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorHeartbeatTaskTest.java @@ -34,7 +34,9 @@ public void testPollCreatesRecords() throws InterruptedException { List records = heartbeatTask.poll(); assertEquals(1, records.size()); Map sourcePartition = records.iterator().next().sourcePartition(); - assertEquals(sourcePartition.get(Heartbeat.SOURCE_CLUSTER_ALIAS_KEY), "testSource"); - assertEquals(sourcePartition.get(Heartbeat.TARGET_CLUSTER_ALIAS_KEY), "testTarget"); + assertEquals(sourcePartition.get(Heartbeat.SOURCE_CLUSTER_ALIAS_KEY), "testSource", + "sourcePartition's " + Heartbeat.SOURCE_CLUSTER_ALIAS_KEY + " record was not created"); + assertEquals(sourcePartition.get(Heartbeat.TARGET_CLUSTER_ALIAS_KEY), "testTarget", + "sourcePartition's " + Heartbeat.TARGET_CLUSTER_ALIAS_KEY + " record was not created"); } -} \ No newline at end of file +} diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorMakerConfigTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorMakerConfigTest.java index f5fe2c3ca32ec..4787ecd95ebda 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorMakerConfigTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorMakerConfigTest.java @@ -137,13 +137,13 @@ public void testIncludesConnectorConfigProperties() { MirrorConnectorConfig connectorConfig = new MirrorConnectorConfig(connectorProps); assertEquals(100, (int) connectorConfig.getInt("tasks.max"), "Connector properties like tasks.max should be passed through to underlying Connectors."); - assertEquals(Arrays.asList("topic-1"), connectorConfig.getList("topics"), + assertEquals(Collections.singletonList("topic-1"), connectorConfig.getList("topics"), "Topics include should be passed through to underlying Connectors."); - assertEquals(Arrays.asList("group-2"), connectorConfig.getList("groups"), + assertEquals(Collections.singletonList("group-2"), connectorConfig.getList("groups"), "Groups include should be passed through to underlying Connectors."); - assertEquals(Arrays.asList("property-3"), connectorConfig.getList("config.properties.exclude"), + assertEquals(Collections.singletonList("property-3"), connectorConfig.getList("config.properties.exclude"), "Config properties exclude should be passed through to underlying Connectors."); - assertEquals(Arrays.asList("FakeMetricsReporter"), connectorConfig.getList("metric.reporters"), + assertEquals(Collections.singletonList("FakeMetricsReporter"), connectorConfig.getList("metric.reporters"), "Metrics reporters should be passed through to underlying Connectors."); assertEquals("DefaultTopicFilter", connectorConfig.getClass("topic.filter.class").getSimpleName(), "Filters should be passed through to underlying Connectors."); @@ -168,13 +168,13 @@ public void testConfigBackwardsCompatibility() { DefaultTopicFilter.TopicFilterConfig filterConfig = new DefaultTopicFilter.TopicFilterConfig(connectorProps); - assertEquals(Arrays.asList("topic3"), filterConfig.getList("topics.exclude"), + assertEquals(Collections.singletonList("topic3"), filterConfig.getList("topics.exclude"), "Topics exclude should be backwards compatible."); - assertEquals(Arrays.asList("group-7"), connectorConfig.getList("groups.exclude"), + assertEquals(Collections.singletonList("group-7"), connectorConfig.getList("groups.exclude"), "Groups exclude should be backwards compatible."); - assertEquals(Arrays.asList("property-3"), connectorConfig.getList("config.properties.exclude"), + assertEquals(Collections.singletonList("property-3"), connectorConfig.getList("config.properties.exclude"), "Config properties exclude should be backwards compatible."); } @@ -193,10 +193,10 @@ public void testConfigBackwardsCompatibilitySourceTarget() { DefaultTopicFilter.TopicFilterConfig filterConfig = new DefaultTopicFilter.TopicFilterConfig(connectorProps); - assertEquals(Arrays.asList("topic3"), filterConfig.getList("topics.exclude"), + assertEquals(Collections.singletonList("topic3"), filterConfig.getList("topics.exclude"), "Topics exclude should be backwards compatible."); - assertEquals(Arrays.asList("group-7"), connectorConfig.getList("groups.exclude"), + assertEquals(Collections.singletonList("group-7"), connectorConfig.getList("groups.exclude"), "Groups exclude should be backwards compatible."); } @@ -213,7 +213,7 @@ public void testIncludesTopicFilterProperties() { new DefaultTopicFilter.TopicFilterConfig(connectorProps); assertEquals(Arrays.asList("topic1", "topic2"), filterConfig.getList("topics"), "source->target.topics should be passed through to TopicFilters."); - assertEquals(Arrays.asList("topic3"), filterConfig.getList("topics.exclude"), + assertEquals(Collections.singletonList("topic3"), filterConfig.getList("topics.exclude"), "source->target.topics.exclude should be passed through to TopicFilters."); } diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorSourceConnectorTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorSourceConnectorTest.java index 42d7951cd60fc..68d149c755ff8 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorSourceConnectorTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorSourceConnectorTest.java @@ -167,13 +167,13 @@ public void testMirrorSourceConnectorTaskConfig() { // t3 -> [t0p2, t0p5, t1p0, t2p1] Map t1 = output.get(0); - assertEquals("t0-0,t0-3,t0-6,t1-1", t1.get(TASK_TOPIC_PARTITIONS)); + assertEquals("t0-0,t0-3,t0-6,t1-1", t1.get(TASK_TOPIC_PARTITIONS), "Config for t1 is incorrect"); Map t2 = output.get(1); - assertEquals("t0-1,t0-4,t0-7,t2-0", t2.get(TASK_TOPIC_PARTITIONS)); + assertEquals("t0-1,t0-4,t0-7,t2-0", t2.get(TASK_TOPIC_PARTITIONS), "Config for t2 is incorrect"); Map t3 = output.get(2); - assertEquals("t0-2,t0-5,t1-0,t2-1", t3.get(TASK_TOPIC_PARTITIONS)); + assertEquals("t0-2,t0-5,t1-0,t2-1", t3.get(TASK_TOPIC_PARTITIONS), "Config for t3 is incorrect"); } @Test @@ -201,7 +201,7 @@ public void testRefreshTopicPartitions() throws Exception { Map expectedPartitionCounts = new HashMap<>(); expectedPartitionCounts.put("source.topic", 1L); Map configMap = MirrorSourceConnector.configToMap(topicConfig); - assertEquals(2, configMap.size()); + assertEquals(2, configMap.size(), "configMap has incorrect size"); Map expectedNewTopics = new HashMap<>(); expectedNewTopics.put("source.topic", new NewTopic("source.topic", 1, (short) 0).configs(configMap)); diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorSourceTaskTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorSourceTaskTest.java index 9cf09f8c4cfd0..feb2f7fb6ba68 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorSourceTaskTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/MirrorSourceTaskTest.java @@ -54,15 +54,22 @@ public void testSerde() { MirrorSourceTask mirrorSourceTask = new MirrorSourceTask(null, null, "cluster7", new DefaultReplicationPolicy(), 50); SourceRecord sourceRecord = mirrorSourceTask.convertRecord(consumerRecord); - assertEquals("cluster7.topic1", sourceRecord.topic()); - assertEquals(2, sourceRecord.kafkaPartition().intValue()); - assertEquals(new TopicPartition("topic1", 2), MirrorUtils.unwrapPartition(sourceRecord.sourcePartition())); - assertEquals(3L, MirrorUtils.unwrapOffset(sourceRecord.sourceOffset()).longValue()); - assertEquals(4L, sourceRecord.timestamp().longValue()); - assertEquals(key, sourceRecord.key()); - assertEquals(value, sourceRecord.value()); - assertEquals(headers.lastHeader("header1").value(), sourceRecord.headers().lastWithName("header1").value()); - assertEquals(headers.lastHeader("header2").value(), sourceRecord.headers().lastWithName("header2").value()); + assertEquals("cluster7.topic1", sourceRecord.topic(), + "Failure on cluster7.topic1 consumerRecord serde"); + assertEquals(2, sourceRecord.kafkaPartition().intValue(), + "sourceRecord kafka partition is incorrect"); + assertEquals(new TopicPartition("topic1", 2), MirrorUtils.unwrapPartition(sourceRecord.sourcePartition()), + "topic1 unwrapped from sourcePartition is incorrect"); + assertEquals(3L, MirrorUtils.unwrapOffset(sourceRecord.sourceOffset()).longValue(), + "sourceRecord's sourceOffset is incorrect"); + assertEquals(4L, sourceRecord.timestamp().longValue(), + "sourceRecord's timestamp is incorrect"); + assertEquals(key, sourceRecord.key(), "sourceRecord's key is incorrect"); + assertEquals(value, sourceRecord.value(), "sourceRecord's value is incorrect"); + assertEquals(headers.lastHeader("header1").value(), sourceRecord.headers().lastWithName("header1").value(), + "sourceRecord's header1 is incorrect"); + assertEquals(headers.lastHeader("header2").value(), sourceRecord.headers().lastWithName("header2").value(), + "sourceRecord's header2 is incorrect"); } @Test @@ -86,16 +93,16 @@ public void testZeroOffsetSync() { MirrorSourceTask.PartitionState partitionState = new MirrorSourceTask.PartitionState(0); // if max offset lag is zero, should always emit offset syncs - assertTrue(partitionState.update(0, 100)); - assertTrue(partitionState.update(2, 102)); - assertTrue(partitionState.update(3, 153)); - assertTrue(partitionState.update(4, 154)); - assertTrue(partitionState.update(5, 155)); - assertTrue(partitionState.update(6, 207)); - assertTrue(partitionState.update(2, 208)); - assertTrue(partitionState.update(3, 209)); - assertTrue(partitionState.update(4, 3)); - assertTrue(partitionState.update(5, 4)); + assertTrue(partitionState.update(0, 100), "zeroOffsetSync downStreamOffset 100 is incorrect"); + assertTrue(partitionState.update(2, 102), "zeroOffsetSync downStreamOffset 102 is incorrect"); + assertTrue(partitionState.update(3, 153), "zeroOffsetSync downStreamOffset 153 is incorrect"); + assertTrue(partitionState.update(4, 154), "zeroOffsetSync downStreamOffset 154 is incorrect"); + assertTrue(partitionState.update(5, 155), "zeroOffsetSync downStreamOffset 155 is incorrect"); + assertTrue(partitionState.update(6, 207), "zeroOffsetSync downStreamOffset 207 is incorrect"); + assertTrue(partitionState.update(2, 208), "zeroOffsetSync downStreamOffset 208 is incorrect"); + assertTrue(partitionState.update(3, 209), "zeroOffsetSync downStreamOffset 209 is incorrect"); + assertTrue(partitionState.update(4, 3), "zeroOffsetSync downStreamOffset 3 is incorrect"); + assertTrue(partitionState.update(5, 4), "zeroOffsetSync downStreamOffset 4 is incorrect"); } @Test @@ -134,13 +141,16 @@ public void testPoll() { for (int i = 0; i < sourceRecords.size(); i++) { SourceRecord sourceRecord = sourceRecords.get(i); ConsumerRecord consumerRecord = consumerRecordsList.get(i); - assertEquals(consumerRecord.key(), sourceRecord.key()); - assertEquals(consumerRecord.value(), sourceRecord.value()); + assertEquals(consumerRecord.key(), sourceRecord.key(), + "consumerRecord key does not equal sourceRecord key"); + assertEquals(consumerRecord.value(), sourceRecord.value(), + "consumerRecord value does not equal sourceRecord value"); // We expect that the topicname will be based on the replication policy currently used assertEquals(replicationPolicy.formatRemoteTopic(sourceClusterName, topicName), - sourceRecord.topic()); + sourceRecord.topic(), "topicName not the same as the current replicationPolicy"); // We expect that MirrorMaker will keep the same partition assignment - assertEquals(consumerRecord.partition(), sourceRecord.kafkaPartition().intValue()); + assertEquals(consumerRecord.partition(), sourceRecord.kafkaPartition().intValue(), + "partition assignment not the same as the current replicationPolicy"); // Check header values List

expectedHeaders = new ArrayList<>(); consumerRecord.headers().forEach(expectedHeaders::add); @@ -155,8 +165,10 @@ private void compareHeaders(List
expectedHeaders, List can't translate - assertEquals(-1, store.translateDownstream(tp, 5)); + assertEquals(-1, store.translateDownstream(tp, 5), + "Expected old offset to not translate"); // Downstream offsets reset store.sync(tp, 200, 10); - assertEquals(store.translateDownstream(tp, 200), 10); + assertEquals(store.translateDownstream(tp, 200), 10, + "Failure in resetting translation of downstream offset"); // Upstream offsets reset store.sync(tp, 20, 20); - assertEquals(store.translateDownstream(tp, 20), 20); + assertEquals(store.translateDownstream(tp, 20), 20, + "Failure in resetting translation of upstream offset"); } } diff --git a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/OffsetSyncTest.java b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/OffsetSyncTest.java index 33f3ab0d2be51..dc7efe291af83 100644 --- a/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/OffsetSyncTest.java +++ b/connect/mirror/src/test/java/org/apache/kafka/connect/mirror/OffsetSyncTest.java @@ -32,8 +32,11 @@ public void testSerde() { byte[] value = offsetSync.recordValue(); ConsumerRecord record = new ConsumerRecord<>("any-topic", 6, 7, key, value); OffsetSync deserialized = OffsetSync.deserializeRecord(record); - assertEquals(offsetSync.topicPartition(), deserialized.topicPartition()); - assertEquals(offsetSync.upstreamOffset(), deserialized.upstreamOffset()); - assertEquals(offsetSync.downstreamOffset(), deserialized.downstreamOffset()); + assertEquals(offsetSync.topicPartition(), deserialized.topicPartition(), + "Failure on offset sync topic partition serde"); + assertEquals(offsetSync.upstreamOffset(), deserialized.upstreamOffset(), + "Failure on upstream offset serde"); + assertEquals(offsetSync.downstreamOffset(), deserialized.downstreamOffset(), + "Failure on downstream offset serde"); } } diff --git a/core/src/main/scala/kafka/admin/ConsumerGroupCommand.scala b/core/src/main/scala/kafka/admin/ConsumerGroupCommand.scala index 2c0dc8c423ef7..6c6090f006dee 100755 --- a/core/src/main/scala/kafka/admin/ConsumerGroupCommand.scala +++ b/core/src/main/scala/kafka/admin/ConsumerGroupCommand.scala @@ -21,7 +21,6 @@ import java.time.{Duration, Instant} import java.util.Properties import com.fasterxml.jackson.dataformat.csv.CsvMapper import com.fasterxml.jackson.module.scala.DefaultScalaModule -import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper import kafka.utils._ import kafka.utils.Implicits._ import org.apache.kafka.clients.admin._ @@ -146,7 +145,7 @@ object ConsumerGroupCommand extends Logging { } // Example: CsvUtils().readerFor[CsvRecordWithoutGroup] private[admin] case class CsvUtils() { - val mapper = new CsvMapper with ScalaObjectMapper + val mapper = new CsvMapper mapper.registerModule(DefaultScalaModule) def readerFor[T <: CsvRecord : ClassTag] = { val schema = getSchema[T] diff --git a/core/src/main/scala/kafka/admin/ReassignPartitionsCommand.scala b/core/src/main/scala/kafka/admin/ReassignPartitionsCommand.scala index 9937558abe32a..186a4313af08e 100755 --- a/core/src/main/scala/kafka/admin/ReassignPartitionsCommand.scala +++ b/core/src/main/scala/kafka/admin/ReassignPartitionsCommand.scala @@ -22,16 +22,14 @@ import java.util.concurrent.ExecutionException import kafka.common.AdminCommandFailedException import kafka.log.LogConfig -import kafka.server.{ConfigType, DynamicConfig} +import kafka.server.DynamicConfig import kafka.utils.{CommandDefaultOptions, CommandLineUtils, CoreUtils, Exit, Json, Logging} import kafka.utils.Implicits._ import kafka.utils.json.JsonValue -import kafka.zk.{AdminZkClient, KafkaZkClient} import org.apache.kafka.clients.admin.AlterConfigOp.OpType import org.apache.kafka.clients.admin.{Admin, AdminClientConfig, AlterConfigOp, ConfigEntry, NewPartitionReassignment, PartitionReassignment, TopicDescription} import org.apache.kafka.common.config.ConfigResource import org.apache.kafka.common.errors.{ReplicaNotAvailableException, UnknownTopicOrPartitionException} -import org.apache.kafka.common.security.JaasUtils import org.apache.kafka.common.utils.{Time, Utils} import org.apache.kafka.common.{KafkaException, KafkaFuture, TopicPartition, TopicPartitionReplica} @@ -194,33 +192,18 @@ object ReassignPartitionsCommand extends Logging { def main(args: Array[String]): Unit = { val opts = validateAndParseArgs(args) - var toClose: Option[AutoCloseable] = None var failed = true + var adminClient: Admin = null try { - if (opts.options.has(opts.bootstrapServerOpt)) { - if (opts.options.has(opts.zkConnectOpt)) { - println("Warning: ignoring deprecated --zookeeper option because " + - "--bootstrap-server was specified. The --zookeeper option will " + - "be removed in a future version of Kafka.") - } - val props = if (opts.options.has(opts.commandConfigOpt)) - Utils.loadProps(opts.options.valueOf(opts.commandConfigOpt)) - else - new util.Properties() - props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, opts.options.valueOf(opts.bootstrapServerOpt)) - props.putIfAbsent(AdminClientConfig.CLIENT_ID_CONFIG, "reassign-partitions-tool") - val adminClient = Admin.create(props) - toClose = Some(adminClient) - handleAction(adminClient, opts) - } else { - println("Warning: --zookeeper is deprecated, and will be removed in a future " + - "version of Kafka.") - val zkClient = KafkaZkClient(opts.options.valueOf(opts.zkConnectOpt), - JaasUtils.isZkSaslEnabled, 30000, 30000, Int.MaxValue, Time.SYSTEM) - toClose = Some(zkClient) - handleAction(zkClient, opts) - } + val props = if (opts.options.has(opts.commandConfigOpt)) + Utils.loadProps(opts.options.valueOf(opts.commandConfigOpt)) + else + new util.Properties() + props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, opts.options.valueOf(opts.bootstrapServerOpt)) + props.putIfAbsent(AdminClientConfig.CLIENT_ID_CONFIG, "reassign-partitions-tool") + adminClient = Admin.create(props) + handleAction(adminClient, opts) failed = false } catch { case e: TerseReassignmentFailureException => @@ -229,9 +212,10 @@ object ReassignPartitionsCommand extends Logging { println("Error: " + e.getMessage) println(Utils.stackTrace(e)) } finally { - // Close the AdminClient or ZooKeeper client, as appropriate. // It's good to do this after printing any error stack trace. - toClose.foreach(_.close()) + if (adminClient != null) { + adminClient.close() + } } // If the command failed, exit with a non-zero exit code. if (failed) { @@ -269,26 +253,6 @@ object ReassignPartitionsCommand extends Logging { } } - private def handleAction(zkClient: KafkaZkClient, - opts: ReassignPartitionsCommandOptions): Unit = { - if (opts.options.has(opts.verifyOpt)) { - verifyAssignment(zkClient, - Utils.readFileAsString(opts.options.valueOf(opts.reassignmentJsonFileOpt)), - opts.options.has(opts.preserveThrottlesOpt)) - } else if (opts.options.has(opts.generateOpt)) { - generateAssignment(zkClient, - Utils.readFileAsString(opts.options.valueOf(opts.topicsToMoveJsonFileOpt)), - opts.options.valueOf(opts.brokerListOpt), - !opts.options.has(opts.disableRackAware)) - } else if (opts.options.has(opts.executeOpt)) { - executeAssignment(zkClient, - Utils.readFileAsString(opts.options.valueOf(opts.reassignmentJsonFileOpt)), - opts.options.valueOf(opts.interBrokerThrottleOpt)) - } else { - throw new RuntimeException("Unsupported action.") - } - } - /** * A result returned from verifyAssignment. * @@ -345,50 +309,6 @@ object ReassignPartitionsCommand extends Logging { (partStates, partsOngoing) } - /** - * The deprecated entry point for the --verify command. - * - * @param zkClient The ZooKeeper client to use. - * @param jsonString The JSON string to use for the topics and partitions to verify. - * @param preserveThrottles True if we should avoid changing topic or broker throttles. - * - * @return A result that is useful for testing. Note that anything that - * would require AdminClient to see will be left out of this result. - */ - def verifyAssignment(zkClient: KafkaZkClient, jsonString: String, preserveThrottles: Boolean) - : VerifyAssignmentResult = { - val (targetParts, targetLogDirs) = parsePartitionReassignmentData(jsonString) - if (targetLogDirs.nonEmpty) { - throw new AdminCommandFailedException("bootstrap-server needs to be provided when " + - "replica reassignments are present.") - } - println("Warning: because you are using the deprecated --zookeeper option, the results " + - "may be incomplete. Use --bootstrap-server instead for more accurate results.") - val (partStates, partsOngoing) = verifyPartitionAssignments(zkClient, targetParts.toMap) - if (!partsOngoing && !preserveThrottles) { - clearAllThrottles(zkClient, targetParts) - } - VerifyAssignmentResult(partStates, partsOngoing, Map.empty, false) - } - - /** - * Verify the partition reassignments specified by the user. - * - * @param zkClient The ZooKeeper client to use. - * @param targets The partition reassignments specified by the user. - * - * @return A tuple of partition states and whether there are any - * ongoing reassignments found in the legacy reassign - * partitions ZNode. - */ - def verifyPartitionAssignments(zkClient: KafkaZkClient, - targets: Map[TopicPartition, Seq[Int]]) - : (Map[TopicPartition, PartitionReassignmentState], Boolean) = { - val (partStates, partsOngoing) = findPartitionReassignmentStates(zkClient, targets) - println(partitionReassignmentStatesToString(partStates)) - (partStates, partsOngoing) - } - def compareTopicPartitions(a: TopicPartition, b: TopicPartition): Boolean = { (a.topic(), a.partition()) < (b.topic(), b.partition()) } @@ -492,32 +412,6 @@ object ReassignPartitionsCommand extends Logging { } } - /** - * Find the state of the specified partition reassignments. - * - * @param zkClient The ZooKeeper client to use. - * @param targetReassignments The reassignments we want to learn about. - * - * @return A tuple containing the reassignment states for each topic - * partition, plus whether there are any ongoing reassignments - * found in the legacy reassign partitions znode. - */ - def findPartitionReassignmentStates(zkClient: KafkaZkClient, - targetReassignments: Map[TopicPartition, Seq[Int]]) - : (Map[TopicPartition, PartitionReassignmentState], Boolean) = { - val partitionsBeingReassigned = zkClient.getPartitionReassignment - val results = new mutable.HashMap[TopicPartition, PartitionReassignmentState]() - targetReassignments.groupBy(_._1.topic).forKeyValue { (topic, partitions) => - val replicasForTopic = zkClient.getReplicaAssignmentForTopics(Set(topic)) - partitions.forKeyValue { (partition, targetReplicas) => - val currentReplicas = replicasForTopic.getOrElse(partition, Seq()) - results.put(partition, new PartitionReassignmentState( - currentReplicas, targetReplicas, !partitionsBeingReassigned.contains(partition))) - } - } - (results, partitionsBeingReassigned.nonEmpty) - } - /** * Verify the replica reassignments specified by the user. * @@ -635,26 +529,6 @@ object ReassignPartitionsCommand extends Logging { clearTopicLevelThrottles(adminClient, topics) } - /** - * Clear all topic-level and broker-level throttles. - * - * @param zkClient The ZooKeeper client to use. - * @param targetParts The target partitions loaded from the JSON file. - */ - def clearAllThrottles(zkClient: KafkaZkClient, - targetParts: Seq[(TopicPartition, Seq[Int])]): Unit = { - val activeBrokers = zkClient.getAllBrokersInCluster.map(_.id).toSet - val brokers = activeBrokers ++ targetParts.flatMap(_._2).toSet - println("Clearing broker-level throttles on broker%s %s".format( - if (brokers.size == 1) "" else "s", brokers.mkString(","))) - clearBrokerLevelThrottles(zkClient, brokers) - - val topics = targetParts.map(_._1.topic()).toSet - println("Clearing topic-level throttles on topic%s %s".format( - if (topics.size == 1) "" else "s", topics.mkString(","))) - clearTopicLevelThrottles(zkClient, topics) - } - /** * Clear all throttles which have been set at the broker level. * @@ -672,22 +546,6 @@ object ReassignPartitionsCommand extends Logging { adminClient.incrementalAlterConfigs(configOps).all().get() } - /** - * Clear all throttles which have been set at the broker level. - * - * @param zkClient The ZooKeeper client to use. - * @param brokers The brokers to clear the throttles for. - */ - def clearBrokerLevelThrottles(zkClient: KafkaZkClient, brokers: Set[Int]): Unit = { - val adminZkClient = new AdminZkClient(zkClient) - for (brokerId <- brokers) { - val configs = adminZkClient.fetchEntityConfig(ConfigType.Broker, brokerId.toString) - if (brokerLevelThrottles.flatMap(throttle => Option(configs.remove(throttle))).nonEmpty) { - adminZkClient.changeBrokerConfig(Seq(brokerId), configs) - } - } - } - /** * Clear the reassignment throttles for the specified topics. * @@ -705,22 +563,6 @@ object ReassignPartitionsCommand extends Logging { adminClient.incrementalAlterConfigs(configOps).all().get() } - /** - * Clear the reassignment throttles for the specified topics. - * - * @param zkClient The ZooKeeper client to use. - * @param topics The topics to clear the throttles for. - */ - def clearTopicLevelThrottles(zkClient: KafkaZkClient, topics: Set[String]): Unit = { - val adminZkClient = new AdminZkClient(zkClient) - for (topic <- topics) { - val configs = adminZkClient.fetchEntityConfig(ConfigType.Topic, topic) - if (topicLevelThrottles.flatMap(throttle => Option(configs.remove(throttle))).nonEmpty) { - adminZkClient.changeTopicConfig(topic, configs) - } - } - } - /** * The entry point for the --generate command. * @@ -749,34 +591,6 @@ object ReassignPartitionsCommand extends Logging { (proposedAssignments, currentAssignments) } - /** - * The legacy entry point for the --generate command. - * - * @param zkClient The ZooKeeper client to use. - * @param reassignmentJson The JSON string to use for the topics to reassign. - * @param brokerListString The comma-separated string of broker IDs to use. - * @param enableRackAwareness True if rack-awareness should be enabled. - * - * @return A tuple containing the proposed assignment and the - * current assignment. - */ - def generateAssignment(zkClient: KafkaZkClient, - reassignmentJson: String, - brokerListString: String, - enableRackAwareness: Boolean) - : (Map[TopicPartition, Seq[Int]], Map[TopicPartition, Seq[Int]]) = { - val (brokersToReassign, topicsToReassign) = - parseGenerateAssignmentArgs(reassignmentJson, brokerListString) - val currentAssignments = zkClient.getReplicaAssignmentForTopics(topicsToReassign.toSet) - val brokerMetadatas = getBrokerMetadata(zkClient, brokersToReassign, enableRackAwareness) - val proposedAssignments = calculateAssignment(currentAssignments, brokerMetadatas) - println("Current partition replica assignment\n%s\n". - format(formatAsReassignmentJson(currentAssignments, Map.empty))) - println("Proposed partition reassignment configuration\n%s". - format(formatAsReassignmentJson(proposedAssignments, Map.empty))) - (proposedAssignments, currentAssignments) - } - /** * Calculate the new partition assignments to suggest in --generate. * @@ -888,25 +702,6 @@ object ReassignPartitionsCommand extends Logging { results } - /** - * Find the metadata for some brokers. - * - * @param zkClient The ZooKeeper client to use. - * @param brokers The brokers to gather metadata about. - * @param enableRackAwareness True if we should return rack information, and throw an - * exception if it is inconsistent. - * - * @return The metadata for each broker that was found. - * Brokers that were not found will be omitted. - */ - def getBrokerMetadata(zkClient: KafkaZkClient, - brokers: Seq[Int], - enableRackAwareness: Boolean): Seq[BrokerMetadata] = { - val adminZkClient = new AdminZkClient(zkClient) - adminZkClient.getBrokerMetadatas(if (enableRackAwareness) - RackAwareMode.Enforced else RackAwareMode.Disabled, Some(brokers)) - } - /** * Parse and validate data gathered from the command-line for --generate * In particular, we parse the JSON and validate that duplicate brokers and @@ -1088,56 +883,6 @@ object ReassignPartitionsCommand extends Logging { } } - /** - * The entry point for the --execute command. - * - * @param zkClient The ZooKeeper client to use. - * @param reassignmentJson The JSON string to use for the topics to reassign. - * @param interBrokerThrottle The inter-broker throttle to use, or a negative number - * to skip using a throttle. - */ - def executeAssignment(zkClient: KafkaZkClient, - reassignmentJson: String, - interBrokerThrottle: Long): Unit = { - val (proposedParts, proposedReplicas) = parseExecuteAssignmentArgs(reassignmentJson) - if (proposedReplicas.nonEmpty) { - throw new AdminCommandFailedException("bootstrap-server needs to be provided when " + - "replica reassignments are present.") - } - verifyReplicasAndBrokersInAssignment(zkClient, proposedParts) - - // Check for the presence of the legacy partition reassignment ZNode. This actually - // won't detect all rebalances... only ones initiated by the legacy method. - // This is a limitation of the legacy ZK API. - val reassignPartitionsInProgress = zkClient.reassignPartitionsInProgress - if (reassignPartitionsInProgress) { - // Note: older versions of this tool would modify the broker quotas here (but not - // topic quotas, for some reason). Since it might interfere with other ongoing - // reassignments, this behavior was dropped as part of the KIP-455 changes. The - // user can still alter existing throttles by resubmitting the current reassignment - // and providing the --additional flag. - throw new TerseReassignmentFailureException(cannotExecuteBecauseOfExistingMessage) - } - val currentParts = zkClient.getReplicaAssignmentForTopics( - proposedParts.map(_._1.topic()).toSet) - println(currentPartitionReplicaAssignmentToString(proposedParts, currentParts)) - - if (interBrokerThrottle >= 0) { - println(youMustRunVerifyPeriodicallyMessage) - val moveMap = calculateProposedMoveMap(Map.empty, proposedParts, currentParts) - val leaderThrottles = calculateLeaderThrottles(moveMap) - val followerThrottles = calculateFollowerThrottles(moveMap) - modifyTopicThrottles(zkClient, leaderThrottles, followerThrottles) - val reassigningBrokers = calculateReassigningBrokers(moveMap) - modifyBrokerThrottles(zkClient, reassigningBrokers, interBrokerThrottle) - println(s"The inter-broker throttle limit was set to ${interBrokerThrottle} B/s") - } - zkClient.createPartitionReassignment(proposedParts) - println("Successfully started partition reassignment%s for %s".format( - if (proposedParts.size == 1) "" else "s", - proposedParts.keySet.toBuffer.sortWith(compareTopicPartitions).mkString(","))) - } - /** * Return the string which we want to print to describe the current partition assignment. * @@ -1154,31 +899,6 @@ object ReassignPartitionsCommand extends Logging { "--reassignment-json-file option during rollback") } - /** - * Verify that the replicas and brokers referenced in the given partition assignment actually - * exist. This is necessary when using the deprecated ZK API, since ZooKeeper itself can't - * validate what we're applying. - * - * @param zkClient The ZooKeeper client to use. - * @param proposedParts The partition assignment. - */ - def verifyReplicasAndBrokersInAssignment(zkClient: KafkaZkClient, - proposedParts: Map[TopicPartition, Seq[Int]]): Unit = { - // check that all partitions in the proposed assignment exist in the cluster - val proposedTopics = proposedParts.map { case (tp, _) => tp.topic } - val existingAssignment = zkClient.getReplicaAssignmentForTopics(proposedTopics.toSet) - val nonExistentPartitions = proposedParts.map { case (tp, _) => tp }.filterNot(existingAssignment.contains) - if (nonExistentPartitions.nonEmpty) - throw new AdminCommandFailedException("The proposed assignment contains non-existent partitions: " + - nonExistentPartitions) - - // check that all brokers in the proposed assignment exist in the cluster - val existingBrokerIDs = zkClient.getSortedBrokerList - val nonExistingBrokerIDs = proposedParts.toMap.values.flatten.filterNot(existingBrokerIDs.contains).toSet - if (nonExistingBrokerIDs.nonEmpty) - throw new AdminCommandFailedException("The proposed assignment contains non-existent brokerIDs: " + nonExistingBrokerIDs.mkString(",")) - } - /** * Execute the given partition reassignments. * @@ -1375,26 +1095,6 @@ object ReassignPartitionsCommand extends Logging { adminClient.incrementalAlterConfigs(configs).all().get() } - /** - * Modify the topic configurations that control inter-broker throttling. - * - * @param zkClient The ZooKeeper client to use. - * @param leaderThrottles A map from topic names to leader throttle configurations. - * @param followerThrottles A map from topic names to follower throttle configurations. - */ - def modifyTopicThrottles(zkClient: KafkaZkClient, - leaderThrottles: Map[String, String], - followerThrottles: Map[String, String]): Unit = { - val adminZkClient = new AdminZkClient(zkClient) - val topicNames = leaderThrottles.keySet ++ followerThrottles.keySet - topicNames.foreach { topicName => - val configs = adminZkClient.fetchEntityConfig(ConfigType.Topic, topicName) - leaderThrottles.get(topicName).map(configs.put(topicLevelLeaderThrottle, _)) - followerThrottles.get(topicName).map(configs.put(topicLevelFollowerThrottle, _)) - adminZkClient.changeTopicConfig(topicName, configs) - } - } - private def modifyReassignmentThrottle(admin: Admin, moveMap: MoveMap, interBrokerThrottle: Long): Unit = { val leaderThrottles = calculateLeaderThrottles(moveMap) val followerThrottles = calculateFollowerThrottles(moveMap) @@ -1451,25 +1151,6 @@ object ReassignPartitionsCommand extends Logging { } } - /** - * Modify the broker-level configurations for leader and follower throttling. - * - * @param zkClient The ZooKeeper client to use. - * @param reassigningBrokers The brokers to reconfigure. - * @param interBrokerThrottle The throttle value to set. - */ - def modifyBrokerThrottles(zkClient: KafkaZkClient, - reassigningBrokers: Set[Int], - interBrokerThrottle: Long): Unit = { - val adminZkClient = new AdminZkClient(zkClient) - for (id <- reassigningBrokers) { - val configs = adminZkClient.fetchEntityConfig(ConfigType.Broker, id.toString) - configs.put(brokerLevelLeaderThrottle, interBrokerThrottle.toString) - configs.put(brokerLevelFollowerThrottle, interBrokerThrottle.toString) - adminZkClient.changeBrokerConfig(Seq(id), configs) - } - } - /** * Parse the reassignment JSON string passed to the --execute command. * @@ -1662,12 +1343,7 @@ object ReassignPartitionsCommand extends Logging { } val action = allActions(0) - // Check that we have either the --zookeeper option or the --bootstrap-server set. - // It would be nice to enforce that we can only have one of these options set at once. Unfortunately, - // previous versions of this tool supported setting both options together. To avoid breaking backwards - // compatibility, we will follow suit, for now. This issue will eventually be resolved when we remove - // the --zookeeper option. - if (!opts.options.has(opts.zkConnectOpt) && !opts.options.has(opts.bootstrapServerOpt)) + if (!opts.options.has(opts.bootstrapServerOpt)) CommandLineUtils.printUsageAndDie(opts.parser, "Please specify --bootstrap-server") // Make sure that we have all the required arguments for our action. @@ -1695,14 +1371,12 @@ object ReassignPartitionsCommand extends Logging { opts.bootstrapServerOpt, opts.commandConfigOpt, opts.preserveThrottlesOpt, - opts.zkConnectOpt ), opts.generateOpt -> Seq( opts.bootstrapServerOpt, opts.brokerListOpt, opts.commandConfigOpt, opts.disableRackAware, - opts.zkConnectOpt ), opts.executeOpt -> Seq( opts.additionalOpt, @@ -1711,7 +1385,6 @@ object ReassignPartitionsCommand extends Logging { opts.interBrokerThrottleOpt, opts.replicaAlterLogDirsThrottleOpt, opts.timeoutOpt, - opts.zkConnectOpt ), opts.cancelOpt -> Seq( opts.bootstrapServerOpt, @@ -1726,28 +1399,13 @@ object ReassignPartitionsCommand extends Logging { ) opts.options.specs.forEach(opt => { if (!opt.equals(action) && - !requiredArgs(action).contains(opt) && - !permittedArgs(action).contains(opt)) { + !requiredArgs(action).contains(opt) && + !permittedArgs(action).contains(opt)) { CommandLineUtils.printUsageAndDie(opts.parser, """Option "%s" can't be used with action "%s"""".format(opt, action)) } }) - if (!opts.options.has(opts.bootstrapServerOpt)) { - val bootstrapServerOnlyArgs = Seq( - opts.additionalOpt, - opts.cancelOpt, - opts.commandConfigOpt, - opts.replicaAlterLogDirsThrottleOpt, - opts.listOpt, - opts.timeoutOpt - ) - bootstrapServerOnlyArgs.foreach { - opt => if (opts.options.has(opt)) { - throw new RuntimeException("You must specify --bootstrap-server " + - """when using "%s"""".format(opt)) - } - } - } + opts } @@ -1775,7 +1433,8 @@ object ReassignPartitionsCommand extends Logging { sealed class ReassignPartitionsCommandOptions(args: Array[String]) extends CommandDefaultOptions(args) { // Actions - val verifyOpt = parser.accepts("verify", "Verify if the reassignment completed as specified by the --reassignment-json-file option. If there is a throttle engaged for the replicas specified, and the rebalance has completed, the throttle will be removed") + val verifyOpt = parser.accepts("verify", "Verify if the reassignment completed as specified by the " + + "--reassignment-json-file option. If there is a throttle engaged for the replicas specified, and the rebalance has completed, the throttle will be removed") val generateOpt = parser.accepts("generate", "Generate a candidate partition reassignment configuration." + " Note that this only generates a candidate assignment, it does not execute it.") val executeOpt = parser.accepts("execute", "Kick off the reassignment as specified by the --reassignment-json-file option.") @@ -1783,21 +1442,16 @@ object ReassignPartitionsCommand extends Logging { val listOpt = parser.accepts("list", "List all active partition reassignments.") // Arguments - val bootstrapServerOpt = parser.accepts("bootstrap-server", "the server(s) to use for bootstrapping. REQUIRED if " + - "an absolute path of the log directory is specified for any replica in the reassignment json file, " + - "or if --zookeeper is not given.") + val bootstrapServerOpt = parser.accepts("bootstrap-server", "REQUIRED: the server(s) to use for bootstrapping.") .withRequiredArg .describedAs("Server(s) to use for bootstrapping") .ofType(classOf[String]) + val commandConfigOpt = parser.accepts("command-config", "Property file containing configs to be passed to Admin Client.") .withRequiredArg .describedAs("Admin client property file") .ofType(classOf[String]) - val zkConnectOpt = parser.accepts("zookeeper", "DEPRECATED: The connection string for the zookeeper connection in the " + - "form host:port. Multiple URLS can be given to allow fail-over. Please use --bootstrap-server instead.") - .withRequiredArg - .describedAs("urls") - .ofType(classOf[String]) + val reassignmentJsonFileOpt = parser.accepts("reassignment-json-file", "The JSON file with the partition reassignment configuration" + "The format to use is - \n" + "{\"partitions\":\n\t[{\"topic\": \"foo\",\n\t \"partition\": 1,\n\t \"replicas\": [1,2,3],\n\t \"log_dirs\": [\"dir1\",\"dir2\",\"dir3\"] }],\n\"version\":1\n}\n" + diff --git a/core/src/main/scala/kafka/admin/TopicCommand.scala b/core/src/main/scala/kafka/admin/TopicCommand.scala index 9cd9007e09ff1..b569e766e03b7 100755 --- a/core/src/main/scala/kafka/admin/TopicCommand.scala +++ b/core/src/main/scala/kafka/admin/TopicCommand.scala @@ -23,23 +23,18 @@ import java.util.{Collections, Properties} import joptsimple._ import kafka.common.AdminCommandFailedException import kafka.log.LogConfig -import kafka.server.ConfigType -import kafka.utils.Implicits._ import kafka.utils._ -import kafka.zk.{AdminZkClient, KafkaZkClient} import org.apache.kafka.clients.CommonClientConfigs import org.apache.kafka.clients.admin.CreatePartitionsOptions import org.apache.kafka.clients.admin.CreateTopicsOptions import org.apache.kafka.clients.admin.DeleteTopicsOptions -import org.apache.kafka.clients.admin.{Admin, ConfigEntry, ListTopicsOptions, NewPartitions, NewTopic, PartitionReassignment, Config => JConfig} -import org.apache.kafka.common.{Node, TopicPartition, TopicPartitionInfo, Uuid} +import org.apache.kafka.clients.admin.{Admin, ListTopicsOptions, NewPartitions, NewTopic, PartitionReassignment, Config => JConfig} +import org.apache.kafka.common.{TopicPartition, TopicPartitionInfo, Uuid} import org.apache.kafka.common.config.ConfigResource.Type import org.apache.kafka.common.config.{ConfigResource, TopicConfig} -import org.apache.kafka.common.errors.{ClusterAuthorizationException, InvalidTopicException, TopicExistsException, UnsupportedVersionException} +import org.apache.kafka.common.errors.{ClusterAuthorizationException, TopicExistsException, UnsupportedVersionException} import org.apache.kafka.common.internals.Topic -import org.apache.kafka.common.security.JaasUtils -import org.apache.kafka.common.utils.{Time, Utils} -import org.apache.zookeeper.KeeperException.NodeExistsException +import org.apache.kafka.common.utils.Utils import scala.jdk.CollectionConverters._ import scala.collection._ @@ -52,10 +47,7 @@ object TopicCommand extends Logging { val opts = new TopicCommandOptions(args) opts.checkArgs() - val topicService = if (opts.zkConnect.isDefined) - ZookeeperTopicService(opts.zkConnect) - else - AdminClientTopicService(opts.commandConfig, opts.bootstrapServer) + val topicService = TopicService(opts.commandConfig, opts.bootstrapServer) var exitCode = 0 try { @@ -204,23 +196,7 @@ object TopicCommand extends Logging { } } - trait TopicService extends AutoCloseable { - def createTopic(opts: TopicCommandOptions): Unit = { - val topic = new CommandTopicPartition(opts) - if (Topic.hasCollisionChars(topic.name)) - println("WARNING: Due to limitations in metric names, topics with a period ('.') or underscore ('_') could " + - "collide. To avoid issues it is best to use either, but not both.") - createTopic(topic) - } - def createTopic(topic: CommandTopicPartition): Unit - def listTopics(opts: TopicCommandOptions): Unit - def alterTopic(opts: TopicCommandOptions): Unit - def describeTopic(opts: TopicCommandOptions): Unit - def deleteTopic(opts: TopicCommandOptions): Unit - def getTopics(topicIncludelist: Option[String], excludeInternalTopics: Boolean = false): Seq[String] - } - - object AdminClientTopicService { + object TopicService { def createAdminClient(commandConfig: Properties, bootstrapServer: Option[String]): Admin = { bootstrapServer match { case Some(serverList) => commandConfig.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, serverList) @@ -229,13 +205,21 @@ object TopicCommand extends Logging { Admin.create(commandConfig) } - def apply(commandConfig: Properties, bootstrapServer: Option[String]): AdminClientTopicService = - new AdminClientTopicService(createAdminClient(commandConfig, bootstrapServer)) + def apply(commandConfig: Properties, bootstrapServer: Option[String]): TopicService = + new TopicService(createAdminClient(commandConfig, bootstrapServer)) } - case class AdminClientTopicService private (adminClient: Admin) extends TopicService { + case class TopicService private (adminClient: Admin) extends AutoCloseable { + + def createTopic(opts: TopicCommandOptions): Unit = { + val topic = new CommandTopicPartition(opts) + if (Topic.hasCollisionChars(topic.name)) + println("WARNING: Due to limitations in metric names, topics with a period ('.') or underscore ('_') could " + + "collide. To avoid issues it is best to use either, but not both.") + createTopic(topic) + } - override def createTopic(topic: CommandTopicPartition): Unit = { + def createTopic(topic: CommandTopicPartition): Unit = { if (topic.replicationFactor.exists(rf => rf > Short.MaxValue || rf < 1)) throw new IllegalArgumentException(s"The replication factor must be between 1 and ${Short.MaxValue} inclusive") if (topic.partitions.exists(partitions => partitions < 1)) @@ -270,11 +254,11 @@ object TopicCommand extends Logging { } } - override def listTopics(opts: TopicCommandOptions): Unit = { + def listTopics(opts: TopicCommandOptions): Unit = { println(getTopics(opts.topic, opts.excludeInternalTopics).mkString("\n")) } - override def alterTopic(opts: TopicCommandOptions): Unit = { + def alterTopic(opts: TopicCommandOptions): Unit = { val topic = new CommandTopicPartition(opts) val topics = getTopics(opts.topic, opts.excludeInternalTopics) ensureTopicExists(topics, opts.topic, !opts.ifExists) @@ -298,7 +282,7 @@ object TopicCommand extends Logging { } } - private def listAllReassignments(topicPartitions: util.Set[TopicPartition]): Map[TopicPartition, PartitionReassignment] = { + def listAllReassignments(topicPartitions: util.Set[TopicPartition]): Map[TopicPartition, PartitionReassignment] = { try { adminClient.listPartitionReassignments(topicPartitions).reassignments().get().asScala } catch { @@ -312,7 +296,7 @@ object TopicCommand extends Logging { } } - override def describeTopic(opts: TopicCommandOptions): Unit = { + def describeTopic(opts: TopicCommandOptions): Unit = { val topics = getTopics(opts.topic, opts.excludeInternalTopics) ensureTopicExists(topics, opts.topic, !opts.ifExists) @@ -354,14 +338,14 @@ object TopicCommand extends Logging { } } - override def deleteTopic(opts: TopicCommandOptions): Unit = { + def deleteTopic(opts: TopicCommandOptions): Unit = { val topics = getTopics(opts.topic, opts.excludeInternalTopics) ensureTopicExists(topics, opts.topic, !opts.ifExists) adminClient.deleteTopics(topics.asJavaCollection, new DeleteTopicsOptions().retryOnQuotaViolation(false)) .all().get() } - override def getTopics(topicIncludelist: Option[String], excludeInternalTopics: Boolean = false): Seq[String] = { + def getTopics(topicIncludelist: Option[String], excludeInternalTopics: Boolean = false): Seq[String] = { val allTopics = if (excludeInternalTopics) { adminClient.listTopics() } else { @@ -370,160 +354,7 @@ object TopicCommand extends Logging { doGetTopics(allTopics.names().get().asScala.toSeq.sorted, topicIncludelist, excludeInternalTopics) } - override def close(): Unit = adminClient.close() - } - - object ZookeeperTopicService { - def apply(zkConnect: Option[String]): ZookeeperTopicService = - new ZookeeperTopicService(KafkaZkClient(zkConnect.get, JaasUtils.isZkSaslEnabled, 30000, 30000, - Int.MaxValue, Time.SYSTEM)) - } - - case class ZookeeperTopicService(zkClient: KafkaZkClient) extends TopicService { - - override def createTopic(topic: CommandTopicPartition): Unit = { - val adminZkClient = new AdminZkClient(zkClient) - try { - if (topic.hasReplicaAssignment) - adminZkClient.createTopicWithAssignment(topic.name, topic.configsToAdd, topic.replicaAssignment.get) - else - adminZkClient.createTopic(topic.name, topic.partitions.get, topic.replicationFactor.get, topic.configsToAdd, topic.rackAwareMode) - println(s"Created topic ${topic.name}.") - } catch { - case e: TopicExistsException => if (!topic.ifTopicDoesntExist()) throw e - } - } - - override def listTopics(opts: TopicCommandOptions): Unit = { - val topics = getTopics(opts.topic, opts.excludeInternalTopics) - for(topic <- topics) { - if (zkClient.isTopicMarkedForDeletion(topic)) - println(s"$topic - marked for deletion") - else - println(topic) - } - } - - override def alterTopic(opts: TopicCommandOptions): Unit = { - val topics = getTopics(opts.topic, opts.excludeInternalTopics) - val tp = new CommandTopicPartition(opts) - ensureTopicExists(topics, opts.topic, !opts.ifExists) - val adminZkClient = new AdminZkClient(zkClient) - topics.foreach { topic => - val configs = adminZkClient.fetchEntityConfig(ConfigType.Topic, topic) - if(opts.topicConfig.isDefined || opts.configsToDelete.isDefined) { - println("WARNING: Altering topic configuration from this script has been deprecated and may be removed in future releases.") - println(" Going forward, please use kafka-configs.sh for this functionality") - - // compile the final set of configs - configs ++= tp.configsToAdd - tp.configsToDelete.foreach(config => configs.remove(config)) - adminZkClient.changeTopicConfig(topic, configs) - println(s"Updated config for topic $topic.") - } - - if(tp.hasPartitions) { - if (Topic.isInternal(topic)) { - throw new IllegalArgumentException(s"The number of partitions for the internal topic $topic cannot be changed.") - } - println("WARNING: If partitions are increased for a topic that has a key, the partition " + - "logic or ordering of the messages will be affected") - val existingAssignment = zkClient.getFullReplicaAssignmentForTopics(immutable.Set(topic)).map { - case (topicPartition, assignment) => topicPartition.partition -> assignment - } - if (existingAssignment.isEmpty) - throw new InvalidTopicException(s"The topic $topic does not exist") - val newAssignment = tp.replicaAssignment.getOrElse(Map()).drop(existingAssignment.size) - val allBrokers = adminZkClient.getBrokerMetadatas() - val partitions: Integer = tp.partitions.getOrElse(1) - adminZkClient.addPartitions(topic, existingAssignment, allBrokers, partitions, Option(newAssignment).filter(_.nonEmpty)) - println("Adding partitions succeeded!") - } - } - } - - override def describeTopic(opts: TopicCommandOptions): Unit = { - val topics = getTopics(opts.topic, opts.excludeInternalTopics) - ensureTopicExists(topics, opts.topic, !opts.ifExists) - val liveBrokers = zkClient.getAllBrokersInCluster.map(broker => broker.id -> broker).toMap - val liveBrokerIds = liveBrokers.keySet - val describeOptions = new DescribeOptions(opts, liveBrokerIds) - val adminZkClient = new AdminZkClient(zkClient) - - for (topic <- topics) { - zkClient.getReplicaAssignmentAndTopicIdForTopics(immutable.Set(topic)).headOption match { - case Some(replicaAssignmentAndTopicId) => - val markedForDeletion = zkClient.isTopicMarkedForDeletion(topic) - if (describeOptions.describeConfigs) { - val configs = adminZkClient.fetchEntityConfig(ConfigType.Topic, topic).asScala - if (!opts.reportOverriddenConfigs || configs.nonEmpty) { - val numPartitions = replicaAssignmentAndTopicId.assignment.size - val replicationFactor = replicaAssignmentAndTopicId.assignment.head._2.replicas.size - val config = new JConfig(configs.map{ case (k, v) => new ConfigEntry(k, v) }.asJavaCollection) - - val topicDesc = TopicDescription(topic, - replicaAssignmentAndTopicId.topicId.getOrElse(Uuid.ZERO_UUID), numPartitions, replicationFactor, config, markedForDeletion) - topicDesc.printDescription() - } - } - if (describeOptions.describePartitions) { - for ((tp, replicaAssignment) <- replicaAssignmentAndTopicId.assignment.toSeq.sortBy(_._1.partition())) { - val assignedReplicas = replicaAssignment.replicas - val (leaderOpt, isr) = zkClient.getTopicPartitionState(tp).map(_.leaderAndIsr) match { - case Some(leaderAndIsr) => (leaderAndIsr.leaderOpt, leaderAndIsr.isr) - case None => (None, Seq.empty[Int]) - } - - def asNode(brokerId: Int): Node = { - liveBrokers.get(brokerId) match { - case Some(broker) => broker.node(broker.endPoints.head.listenerName) - case None => new Node(brokerId, "", -1) - } - } - - val info = new TopicPartitionInfo(tp.partition(), leaderOpt.map(asNode).orNull, - assignedReplicas.map(asNode).toList.asJava, - isr.map(asNode).toList.asJava) - - val partitionDesc = PartitionDescription(topic, info, config = None, markedForDeletion, reassignment = None) - describeOptions.maybePrintPartitionDescription(partitionDesc) - } - } - case None => - println("Topic " + topic + " doesn't exist!") - } - } - } - - override def deleteTopic(opts: TopicCommandOptions): Unit = { - val topics = getTopics(opts.topic, opts.excludeInternalTopics) - ensureTopicExists(topics, opts.topic, !opts.ifExists) - topics.foreach { topic => - try { - if (Topic.isInternal(topic)) { - throw new AdminOperationException(s"Topic $topic is a kafka internal topic and is not allowed to be marked for deletion.") - } else { - zkClient.createDeleteTopicPath(topic) - println(s"Topic $topic is marked for deletion.") - println("Note: This will have no impact if delete.topic.enable is not set to true.") - } - } catch { - case _: NodeExistsException => - println(s"Topic $topic is already marked for deletion.") - case e: AdminOperationException => - throw e - case e: Throwable => - throw new AdminOperationException(s"Error while deleting topic $topic", e) - } - } - } - - override def getTopics(topicIncludelist: Option[String], excludeInternalTopics: Boolean = false): Seq[String] = { - val allTopics = zkClient.getAllTopicsInCluster().toSeq.sorted - doGetTopics(allTopics, topicIncludelist, excludeInternalTopics) - } - - override def close(): Unit = zkClient.close() + def close(): Unit = adminClient.close() } /** @@ -610,20 +441,17 @@ object TopicCommand extends Logging { } class TopicCommandOptions(args: Array[String]) extends CommandDefaultOptions(args) { - private val bootstrapServerOpt = parser.accepts("bootstrap-server", "REQUIRED: The Kafka server to connect to. In case of providing this, a direct Zookeeper connection won't be required.") + private val bootstrapServerOpt = parser.accepts("bootstrap-server", "REQUIRED: The Kafka server to connect to.") .withRequiredArg .describedAs("server to connect to") .ofType(classOf[String]) + private val commandConfigOpt = parser.accepts("command-config", "Property file containing configs to be passed to Admin Client. " + "This is used only with --bootstrap-server option for describing and altering broker configs.") .withRequiredArg .describedAs("command config property file") .ofType(classOf[String]) - private val zkConnectOpt = parser.accepts("zookeeper", "DEPRECATED, The connection string for the zookeeper connection in the form host:port. " + - "Multiple hosts can be given to allow fail-over.") - .withRequiredArg - .describedAs("hosts") - .ofType(classOf[String]) + private val listOpt = parser.accepts("list", "List all available topics.") private val createOpt = parser.accepts("create", "Create a new topic.") private val deleteOpt = parser.accepts("delete", "Delete a topic") @@ -670,9 +498,9 @@ object TopicCommand extends Logging { private val reportUnavailablePartitionsOpt = parser.accepts("unavailable-partitions", "if set when describing topics, only show partitions whose leader is not available") private val reportUnderMinIsrPartitionsOpt = parser.accepts("under-min-isr-partitions", - "if set when describing topics, only show partitions whose isr count is less than the configured minimum. Not supported with the --zookeeper option.") + "if set when describing topics, only show partitions whose isr count is less than the configured minimum.") private val reportAtMinIsrPartitionsOpt = parser.accepts("at-min-isr-partitions", - "if set when describing topics, only show partitions whose isr count is equal to the configured minimum. Not supported with the --zookeeper option.") + "if set when describing topics, only show partitions whose isr count is equal to the configured minimum.") private val topicsWithOverridesOpt = parser.accepts("topics-with-overrides", "if set when describing topics, only show topics that have overridden configs") private val ifExistsOpt = parser.accepts("if-exists", @@ -682,9 +510,6 @@ object TopicCommand extends Logging { private val disableRackAware = parser.accepts("disable-rack-aware", "Disable rack aware replica assignment") - // This is not currently used, but we keep it for compatibility - parser.accepts("force", "Suppress console prompts") - private val excludeInternalTopicOpt = parser.accepts("exclude-internal", "exclude internal topics when running list or describe command. The internal topics will be listed by default") @@ -704,7 +529,6 @@ object TopicCommand extends Logging { def hasDescribeOption: Boolean = has(describeOpt) def hasDeleteOption: Boolean = has(deleteOpt) - def zkConnect: Option[String] = valueAsOption(zkConnectOpt) def bootstrapServer: Option[String] = valueAsOption(bootstrapServerOpt) def commandConfig: Properties = if (has(commandConfigOpt)) Utils.loadProps(options.valueOf(commandConfigOpt)) else new Properties() def topic: Option[String] = valueAsOption(topicOpt) @@ -739,18 +563,15 @@ object TopicCommand extends Logging { CommandLineUtils.printUsageAndDie(parser, "Command must include exactly one action: --list, --describe, --create, --alter or --delete") // check required args - if (has(bootstrapServerOpt) == has(zkConnectOpt)) - throw new IllegalArgumentException("Only one of --bootstrap-server or --zookeeper must be specified") - if (!has(bootstrapServerOpt)) - CommandLineUtils.checkRequiredArgs(parser, options, zkConnectOpt) - if(has(describeOpt) && has(ifExistsOpt)) + throw new IllegalArgumentException("--bootstrap-server must be specified") + if (has(describeOpt) && has(ifExistsOpt)) CommandLineUtils.checkRequiredArgs(parser, options, topicOpt) if (!has(listOpt) && !has(describeOpt)) CommandLineUtils.checkRequiredArgs(parser, options, topicOpt) - if (has(createOpt) && !has(replicaAssignmentOpt) && has(zkConnectOpt)) + if (has(createOpt) && !has(replicaAssignmentOpt)) CommandLineUtils.checkRequiredArgs(parser, options, partitionsOpt, replicationFactorOpt) - if (has(bootstrapServerOpt) && has(alterOpt)) { + if (has(alterOpt)) { CommandLineUtils.checkInvalidArgsSet(parser, options, Set(bootstrapServerOpt, configOpt), Set(alterOpt), Some(kafkaConfigsCanAlterTopicConfigsViaBootstrapServer)) CommandLineUtils.checkRequiredArgs(parser, options, partitionsOpt) @@ -763,13 +584,13 @@ object TopicCommand extends Logging { CommandLineUtils.checkInvalidArgs(parser, options, replicationFactorOpt, allTopicLevelOpts -- Set(createOpt)) CommandLineUtils.checkInvalidArgs(parser, options, replicaAssignmentOpt, allTopicLevelOpts -- Set(createOpt,alterOpt)) if(options.has(createOpt)) - CommandLineUtils.checkInvalidArgs(parser, options, replicaAssignmentOpt, Set(partitionsOpt, replicationFactorOpt)) + CommandLineUtils.checkInvalidArgs(parser, options, replicaAssignmentOpt, Set(partitionsOpt, replicationFactorOpt)) CommandLineUtils.checkInvalidArgs(parser, options, reportUnderReplicatedPartitionsOpt, allTopicLevelOpts -- Set(describeOpt) ++ allReplicationReportOpts - reportUnderReplicatedPartitionsOpt + topicsWithOverridesOpt) CommandLineUtils.checkInvalidArgs(parser, options, reportUnderMinIsrPartitionsOpt, - allTopicLevelOpts -- Set(describeOpt) ++ allReplicationReportOpts - reportUnderMinIsrPartitionsOpt + topicsWithOverridesOpt + zkConnectOpt) + allTopicLevelOpts -- Set(describeOpt) ++ allReplicationReportOpts - reportUnderMinIsrPartitionsOpt + topicsWithOverridesOpt) CommandLineUtils.checkInvalidArgs(parser, options, reportAtMinIsrPartitionsOpt, - allTopicLevelOpts -- Set(describeOpt) ++ allReplicationReportOpts - reportAtMinIsrPartitionsOpt + topicsWithOverridesOpt + zkConnectOpt) + allTopicLevelOpts -- Set(describeOpt) ++ allReplicationReportOpts - reportAtMinIsrPartitionsOpt + topicsWithOverridesOpt) CommandLineUtils.checkInvalidArgs(parser, options, reportUnavailablePartitionsOpt, allTopicLevelOpts -- Set(describeOpt) ++ allReplicationReportOpts - reportUnavailablePartitionsOpt + topicsWithOverridesOpt) CommandLineUtils.checkInvalidArgs(parser, options, topicsWithOverridesOpt, diff --git a/core/src/main/scala/kafka/cluster/Partition.scala b/core/src/main/scala/kafka/cluster/Partition.scala index 7eea0e2bbf656..89cadf4485851 100755 --- a/core/src/main/scala/kafka/cluster/Partition.scala +++ b/core/src/main/scala/kafka/cluster/Partition.scala @@ -18,7 +18,6 @@ package kafka.cluster import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.Optional - import kafka.api.{ApiVersion, LeaderAndIsr} import kafka.common.UnexpectedAppendOffsetException import kafka.controller.{KafkaController, StateChangeLogger} @@ -1019,7 +1018,8 @@ class Partition(val topicPartition: TopicPartition, } } - def appendRecordsToLeader(records: MemoryRecords, origin: AppendOrigin, requiredAcks: Int): LogAppendInfo = { + def appendRecordsToLeader(records: MemoryRecords, origin: AppendOrigin, requiredAcks: Int, + requestLocal: RequestLocal): LogAppendInfo = { val (info, leaderHWIncremented) = inReadLock(leaderIsrUpdateLock) { leaderLogIfLocal match { case Some(leaderLog) => @@ -1033,7 +1033,7 @@ class Partition(val topicPartition: TopicPartition, } val info = leaderLog.appendAsLeader(records, leaderEpoch = this.leaderEpoch, origin, - interBrokerProtocolVersion) + interBrokerProtocolVersion, requestLocal) // we may need to increment high watermark since ISR could be down to 1 (info, maybeIncrementLeaderHW(leaderLog)) diff --git a/core/src/main/scala/kafka/coordinator/group/GroupCoordinator.scala b/core/src/main/scala/kafka/coordinator/group/GroupCoordinator.scala index bb06ca538f9dd..3fc93deb2b07c 100644 --- a/core/src/main/scala/kafka/coordinator/group/GroupCoordinator.scala +++ b/core/src/main/scala/kafka/coordinator/group/GroupCoordinator.scala @@ -18,7 +18,6 @@ package kafka.coordinator.group import java.util.Properties import java.util.concurrent.atomic.AtomicBoolean - import kafka.common.OffsetAndMetadata import kafka.log.LogConfig import kafka.message.ProducerCompressionCodec @@ -92,6 +91,7 @@ class GroupCoordinator(val brokerId: Int, props.put(LogConfig.CleanupPolicyProp, LogConfig.Compact) props.put(LogConfig.SegmentBytesProp, offsetConfig.offsetsTopicSegmentBytes.toString) props.put(LogConfig.CompressionTypeProp, ProducerCompressionCodec.name) + props } @@ -162,7 +162,8 @@ class GroupCoordinator(val brokerId: Int, sessionTimeoutMs: Int, protocolType: String, protocols: List[(String, Array[Byte])], - responseCallback: JoinCallback): Unit = { + responseCallback: JoinCallback, + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { validateGroupStatus(groupId, ApiKeys.JOIN_GROUP).foreach { error => responseCallback(JoinGroupResult(memberId, error)) return @@ -194,7 +195,8 @@ class GroupCoordinator(val brokerId: Int, sessionTimeoutMs, protocolType, protocols, - responseCallback + responseCallback, + requestLocal ) } else { doCurrentMemberJoinGroup( @@ -230,7 +232,8 @@ class GroupCoordinator(val brokerId: Int, sessionTimeoutMs: Int, protocolType: String, protocols: List[(String, Array[Byte])], - responseCallback: JoinCallback + responseCallback: JoinCallback, + requestLocal: RequestLocal ): Unit = { group.inLock { if (group.is(Dead)) { @@ -255,9 +258,9 @@ class GroupCoordinator(val brokerId: Int, sessionTimeoutMs, protocolType, protocols, - responseCallback + responseCallback, + requestLocal ) - case None => doDynamicNewMemberJoinGroup( group, @@ -286,14 +289,15 @@ class GroupCoordinator(val brokerId: Int, sessionTimeoutMs: Int, protocolType: String, protocols: List[(String, Array[Byte])], - responseCallback: JoinCallback + responseCallback: JoinCallback, + requestLocal: RequestLocal ): Unit = { group.currentStaticMemberId(groupInstanceId) match { case Some(oldMemberId) => info(s"Static member with groupInstanceId=$groupInstanceId and unknown member id joins " + s"group ${group.groupId} in ${group.currentState} state. Replacing previously mapped " + s"member $oldMemberId with this groupInstanceId.") - updateStaticMemberAndRebalance(group, oldMemberId, newMemberId, groupInstanceId, protocols, responseCallback) + updateStaticMemberAndRebalance(group, oldMemberId, newMemberId, groupInstanceId, protocols, responseCallback, requestLocal) case None => info(s"Static member with groupInstanceId=$groupInstanceId and unknown member id joins " + @@ -440,7 +444,7 @@ class GroupCoordinator(val brokerId: Int, // force a rebalance if the leader sends JoinGroup; // This allows the leader to trigger rebalances for changes affecting assignment // which do not affect the member metadata (such as topic metadata changes for the consumer) - updateMemberAndRebalance(group, member, protocols, s"leader ${member.memberId} re-joining group during ${group.currentState}", responseCallback) + updateMemberAndRebalance(group, member, protocols, s"Leader ${member.memberId} re-joining group during ${group.currentState}", responseCallback) } else if (!member.matches(protocols)) { updateMemberAndRebalance(group, member, protocols, s"Updating metadata for member ${member.memberId} during ${group.currentState}", responseCallback) } else { @@ -474,7 +478,8 @@ class GroupCoordinator(val brokerId: Int, protocolName: Option[String], groupInstanceId: Option[String], groupAssignment: Map[String, Array[Byte]], - responseCallback: SyncCallback): Unit = { + responseCallback: SyncCallback, + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { validateGroupStatus(groupId, ApiKeys.SYNC_GROUP) match { case Some(error) if error == Errors.COORDINATOR_LOAD_IN_PROGRESS => // The coordinator is loading, which means we've lost the state of the active rebalance and the @@ -489,7 +494,7 @@ class GroupCoordinator(val brokerId: Int, groupManager.getGroup(groupId) match { case None => responseCallback(SyncGroupResult(Errors.UNKNOWN_MEMBER_ID)) case Some(group) => doSyncGroup(group, generation, memberId, protocolType, protocolName, - groupInstanceId, groupAssignment, responseCallback) + groupInstanceId, groupAssignment, requestLocal, responseCallback) } } } @@ -535,6 +540,7 @@ class GroupCoordinator(val brokerId: Int, protocolName: Option[String], groupInstanceId: Option[String], groupAssignment: Map[String, Array[Byte]], + requestLocal: RequestLocal, responseCallback: SyncCallback): Unit = { group.inLock { val validationErrorOpt = validateSyncGroup( @@ -561,7 +567,7 @@ class GroupCoordinator(val brokerId: Int, // if this is the leader, then we can attempt to persist state and transition to stable if (group.isLeader(memberId)) { - info(s"Assignment received from leader for group ${group.groupId} for generation ${group.generationId}. " + + info(s"Assignment received from leader $memberId for group ${group.groupId} for generation ${group.generationId}. " + s"The group has ${group.size} members, ${group.allStaticMembers.size} of which are static.") // fill any missing members with an empty assignment @@ -580,14 +586,14 @@ class GroupCoordinator(val brokerId: Int, if (group.is(CompletingRebalance) && generationId == group.generationId) { if (error != Errors.NONE) { resetAndPropagateAssignmentError(group, error) - maybePrepareRebalance(group, s"error when storing group assignment during SyncGroup (member: $memberId)") + maybePrepareRebalance(group, s"Error when storing group assignment during SyncGroup (member: $memberId)") } else { setAndPropagateAssignment(group, assignment) group.transitionTo(Stable) } } } - }) + }, requestLocal) groupCompletedRebalanceSensor.record() } @@ -610,7 +616,7 @@ class GroupCoordinator(val brokerId: Int, def removeCurrentMemberFromGroup(group: GroupMetadata, memberId: String): Unit = { val member = group.get(memberId) - removeMemberAndUpdateGroup(group, member, s"removing member $memberId on LeaveGroup") + removeMemberAndUpdateGroup(group, member, s"Removing member $memberId on LeaveGroup") removeHeartbeatForLeavingMember(group, member) info(s"Member $member has left group $groupId through explicit `LeaveGroup` request") } @@ -669,7 +675,8 @@ class GroupCoordinator(val brokerId: Int, } } - def handleDeleteGroups(groupIds: Set[String]): Map[String, Errors] = { + def handleDeleteGroups(groupIds: Set[String], + requestLocal: RequestLocal = RequestLocal.NoCaching): Map[String, Errors] = { val groupErrors = mutable.Map.empty[String, Errors] val groupsEligibleForDeletion = mutable.ArrayBuffer[GroupMetadata]() @@ -701,7 +708,8 @@ class GroupCoordinator(val brokerId: Int, } if (groupsEligibleForDeletion.nonEmpty) { - val offsetsRemoved = groupManager.cleanupGroupMetadata(groupsEligibleForDeletion, _.removeAllOffsets()) + val offsetsRemoved = groupManager.cleanupGroupMetadata(groupsEligibleForDeletion, requestLocal, + _.removeAllOffsets()) groupErrors ++= groupsEligibleForDeletion.map(_.groupId -> Errors.NONE).toMap info(s"The following groups were deleted: ${groupsEligibleForDeletion.map(_.groupId).mkString(", ")}. " + s"A total of $offsetsRemoved offsets were removed.") @@ -710,7 +718,8 @@ class GroupCoordinator(val brokerId: Int, groupErrors } - def handleDeleteOffsets(groupId: String, partitions: Seq[TopicPartition]): (Errors, Map[TopicPartition, Errors]) = { + def handleDeleteOffsets(groupId: String, partitions: Seq[TopicPartition], + requestLocal: RequestLocal): (Errors, Map[TopicPartition, Errors]) = { var groupError: Errors = Errors.NONE var partitionErrors: Map[TopicPartition, Errors] = Map() var partitionsEligibleForDeletion: Seq[TopicPartition] = Seq() @@ -748,9 +757,8 @@ class GroupCoordinator(val brokerId: Int, } if (partitionsEligibleForDeletion.nonEmpty) { - val offsetsRemoved = groupManager.cleanupGroupMetadata(Seq(group), group => { - group.removeOffsets(partitionsEligibleForDeletion) - }) + val offsetsRemoved = groupManager.cleanupGroupMetadata(Seq(group), requestLocal, + _.removeOffsets(partitionsEligibleForDeletion)) partitionErrors ++= partitionsEligibleForDeletion.map(_ -> Errors.NONE).toMap @@ -855,14 +863,16 @@ class GroupCoordinator(val brokerId: Int, groupInstanceId: Option[String], generationId: Int, offsetMetadata: immutable.Map[TopicPartition, OffsetAndMetadata], - responseCallback: immutable.Map[TopicPartition, Errors] => Unit): Unit = { + responseCallback: immutable.Map[TopicPartition, Errors] => Unit, + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { validateGroupStatus(groupId, ApiKeys.TXN_OFFSET_COMMIT) match { case Some(error) => responseCallback(offsetMetadata.map { case (k, _) => k -> error }) case None => val group = groupManager.getGroup(groupId).getOrElse { groupManager.addGroup(new GroupMetadata(groupId, Empty, time)) } - doTxnCommitOffsets(group, memberId, groupInstanceId, generationId, producerId, producerEpoch, offsetMetadata, responseCallback) + doTxnCommitOffsets(group, memberId, groupInstanceId, generationId, producerId, producerEpoch, + offsetMetadata, requestLocal, responseCallback) } } @@ -871,7 +881,8 @@ class GroupCoordinator(val brokerId: Int, groupInstanceId: Option[String], generationId: Int, offsetMetadata: immutable.Map[TopicPartition, OffsetAndMetadata], - responseCallback: immutable.Map[TopicPartition, Errors] => Unit): Unit = { + responseCallback: immutable.Map[TopicPartition, Errors] => Unit, + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { validateGroupStatus(groupId, ApiKeys.OFFSET_COMMIT) match { case Some(error) => responseCallback(offsetMetadata.map { case (k, _) => k -> error }) case None => @@ -880,14 +891,16 @@ class GroupCoordinator(val brokerId: Int, if (generationId < 0) { // the group is not relying on Kafka for group management, so allow the commit val group = groupManager.addGroup(new GroupMetadata(groupId, Empty, time)) - doCommitOffsets(group, memberId, groupInstanceId, generationId, offsetMetadata, responseCallback) + doCommitOffsets(group, memberId, groupInstanceId, generationId, offsetMetadata, + responseCallback, requestLocal) } else { // or this is a request coming from an older generation. either way, reject the commit responseCallback(offsetMetadata.map { case (k, _) => k -> Errors.ILLEGAL_GENERATION }) } case Some(group) => - doCommitOffsets(group, memberId, groupInstanceId, generationId, offsetMetadata, responseCallback) + doCommitOffsets(group, memberId, groupInstanceId, generationId, offsetMetadata, + responseCallback, requestLocal) } } } @@ -907,6 +920,7 @@ class GroupCoordinator(val brokerId: Int, producerId: Long, producerEpoch: Short, offsetMetadata: immutable.Map[TopicPartition, OffsetAndMetadata], + requestLocal: RequestLocal, responseCallback: immutable.Map[TopicPartition, Errors] => Unit): Unit = { group.inLock { val validationErrorOpt = validateOffsetCommit( @@ -920,7 +934,8 @@ class GroupCoordinator(val brokerId: Int, if (validationErrorOpt.isDefined) { responseCallback(offsetMetadata.map { case (k, _) => k -> validationErrorOpt.get }) } else { - groupManager.storeOffsets(group, memberId, offsetMetadata, responseCallback, producerId, producerEpoch) + groupManager.storeOffsets(group, memberId, offsetMetadata, responseCallback, producerId, + producerEpoch, requestLocal) } } } @@ -963,7 +978,8 @@ class GroupCoordinator(val brokerId: Int, groupInstanceId: Option[String], generationId: Int, offsetMetadata: immutable.Map[TopicPartition, OffsetAndMetadata], - responseCallback: immutable.Map[TopicPartition, Errors] => Unit): Unit = { + responseCallback: immutable.Map[TopicPartition, Errors] => Unit, + requestLocal: RequestLocal): Unit = { group.inLock { val validationErrorOpt = validateOffsetCommit( group, @@ -985,7 +1001,7 @@ class GroupCoordinator(val brokerId: Int, // on heartbeat response to eventually notify the rebalance in progress signal to the consumer val member = group.get(memberId) completeAndScheduleNextHeartbeatExpiration(group, member) - groupManager.storeOffsets(group, memberId, offsetMetadata, responseCallback) + groupManager.storeOffsets(group, memberId, offsetMetadata, responseCallback, requestLocal = requestLocal) case CompletingRebalance => // We should not receive a commit request if the group has not completed rebalance; @@ -1041,10 +1057,9 @@ class GroupCoordinator(val brokerId: Int, } } - def handleDeletedPartitions(topicPartitions: Seq[TopicPartition]): Unit = { - val offsetsRemoved = groupManager.cleanupGroupMetadata(groupManager.currentGroups, group => { - group.removeOffsets(topicPartitions) - }) + def handleDeletedPartitions(topicPartitions: Seq[TopicPartition], requestLocal: RequestLocal): Unit = { + val offsetsRemoved = groupManager.cleanupGroupMetadata(groupManager.currentGroups, requestLocal, + _.removeOffsets(topicPartitions)) info(s"Removed $offsetsRemoved offsets associated with deleted partitions: ${topicPartitions.mkString(", ")}.") } @@ -1117,9 +1132,9 @@ class GroupCoordinator(val brokerId: Int, * * @param offsetTopicPartitionId The partition we are now leading */ - def onElection(offsetTopicPartitionId: Int): Unit = { - info(s"Elected as the group coordinator for partition $offsetTopicPartitionId") - groupManager.scheduleLoadGroupAndOffsets(offsetTopicPartitionId, onGroupLoaded) + def onElection(offsetTopicPartitionId: Int, coordinatorEpoch: Int): Unit = { + info(s"Elected as the group coordinator for partition $offsetTopicPartitionId in epoch $coordinatorEpoch") + groupManager.scheduleLoadGroupAndOffsets(offsetTopicPartitionId, coordinatorEpoch, onGroupLoaded) } /** @@ -1127,9 +1142,9 @@ class GroupCoordinator(val brokerId: Int, * * @param offsetTopicPartitionId The partition we are no longer leading */ - def onResignation(offsetTopicPartitionId: Int): Unit = { - info(s"Resigned as the group coordinator for partition $offsetTopicPartitionId") - groupManager.removeGroupsForPartition(offsetTopicPartitionId, onGroupUnloaded) + def onResignation(offsetTopicPartitionId: Int, coordinatorEpoch: Option[Int]): Unit = { + info(s"Resigned as the group coordinator for partition $offsetTopicPartitionId in epoch $coordinatorEpoch") + groupManager.removeGroupsForPartition(offsetTopicPartitionId, coordinatorEpoch, onGroupUnloaded) } private def setAndPropagateAssignment(group: GroupMetadata, assignment: Map[String, Array[Byte]]): Unit = { @@ -1235,7 +1250,8 @@ class GroupCoordinator(val brokerId: Int, newMemberId: String, groupInstanceId: String, protocols: List[(String, Array[Byte])], - responseCallback: JoinCallback): Unit = { + responseCallback: JoinCallback, + requestLocal: RequestLocal): Unit = { val currentLeader = group.leaderOrNull val member = group.replaceStaticMember(groupInstanceId, oldMemberId, newMemberId) // Heartbeat of old member id will expire without effect since the group no longer contains that member id. @@ -1287,7 +1303,7 @@ class GroupCoordinator(val brokerId: Int, leaderId = currentLeader, error = Errors.NONE)) } - }) + }, requestLocal) } else { maybePrepareRebalance(group, s"Group's selectedProtocol will change because static member ${member.memberId} with instance id $groupInstanceId joined with change of protocol") } @@ -1411,7 +1427,7 @@ class GroupCoordinator(val brokerId: Int, // This should be safe since there are no active members in an empty generation, so we just warn. warn(s"Failed to write empty metadata for group ${group.groupId}: ${error.message}") } - }) + }, RequestLocal.NoCaching) } else { info(s"Stabilized group ${group.groupId} generation ${group.generationId} " + s"(${Topic.GROUP_METADATA_TOPIC_NAME}-${partitionFor(group.groupId)}) with ${group.size} members") diff --git a/core/src/main/scala/kafka/coordinator/group/GroupMetadataManager.scala b/core/src/main/scala/kafka/coordinator/group/GroupMetadataManager.scala index c054234abb082..78d61eedec003 100644 --- a/core/src/main/scala/kafka/coordinator/group/GroupMetadataManager.scala +++ b/core/src/main/scala/kafka/coordinator/group/GroupMetadataManager.scala @@ -21,9 +21,9 @@ import java.io.PrintStream import java.nio.ByteBuffer import java.nio.charset.StandardCharsets import java.util.Optional -import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.locks.ReentrantLock +import java.util.concurrent.{ConcurrentHashMap, TimeUnit} import com.yammer.metrics.core.Gauge import kafka.api.{ApiVersion, KAFKA_0_10_1_IV0, KAFKA_2_1_IV0, KAFKA_2_1_IV1, KAFKA_2_3_IV0} @@ -31,7 +31,7 @@ import kafka.common.OffsetAndMetadata import kafka.internals.generated.{GroupMetadataValue, OffsetCommitKey, OffsetCommitValue, GroupMetadataKey => GroupMetadataKeyData} import kafka.log.AppendOrigin import kafka.metrics.KafkaMetricsGroup -import kafka.server.{FetchLogEnd, ReplicaManager} +import kafka.server.{FetchLogEnd, ReplicaManager, RequestLocal} import kafka.utils.CoreUtils.inLock import kafka.utils.Implicits._ import kafka.utils._ @@ -86,6 +86,9 @@ class GroupMetadataManager(brokerId: Int, * We use this structure to quickly find the groups which need to be updated by the commit/abort marker. */ private val openGroupsForProducer = mutable.HashMap[Long, mutable.Set[String]]() + /* Track the epoch in which we (un)loaded group state to detect racing LeaderAndIsr requests */ + private [group] val epochForPartitionId = new ConcurrentHashMap[Int, java.lang.Integer]() + /* setup metrics*/ private val partitionLoadSensor = metrics.sensor(GroupMetadataManager.LoadTimeSensor) @@ -240,7 +243,8 @@ class GroupMetadataManager(brokerId: Int, def storeGroup(group: GroupMetadata, groupAssignment: Map[String, Array[Byte]], - responseCallback: Errors => Unit): Unit = { + responseCallback: Errors => Unit, + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { getMagic(partitionFor(group.groupId)) match { case Some(magicValue) => // We always use CREATE_TIME, like the producer. The conversion to LOG_APPEND_TIME (if necessary) happens automatically. @@ -310,7 +314,7 @@ class GroupMetadataManager(brokerId: Int, responseCallback(responseError) } - appendForGroup(group, groupMetadataRecords, putCacheCallback) + appendForGroup(group, groupMetadataRecords, requestLocal, putCacheCallback) case None => responseCallback(Errors.NOT_COORDINATOR) @@ -320,6 +324,7 @@ class GroupMetadataManager(brokerId: Int, private def appendForGroup(group: GroupMetadata, records: Map[TopicPartition, MemoryRecords], + requestLocal: RequestLocal, callback: Map[TopicPartition, PartitionResponse] => Unit): Unit = { // call replica manager to append the group message replicaManager.appendRecords( @@ -329,7 +334,8 @@ class GroupMetadataManager(brokerId: Int, origin = AppendOrigin.Coordinator, entriesPerPartition = records, delayedProduceLock = Some(group.lock), - responseCallback = callback) + responseCallback = callback, + requestLocal = requestLocal) } /** @@ -340,7 +346,8 @@ class GroupMetadataManager(brokerId: Int, offsetMetadata: immutable.Map[TopicPartition, OffsetAndMetadata], responseCallback: immutable.Map[TopicPartition, Errors] => Unit, producerId: Long = RecordBatch.NO_PRODUCER_ID, - producerEpoch: Short = RecordBatch.NO_PRODUCER_EPOCH): Unit = { + producerEpoch: Short = RecordBatch.NO_PRODUCER_EPOCH, + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { // first filter out partitions with offset metadata size exceeding limit val filteredOffsetMetadata = offsetMetadata.filter { case (_, offsetAndMetadata) => validateOffsetMetadataLength(offsetAndMetadata.metadata) @@ -467,7 +474,7 @@ class GroupMetadataManager(brokerId: Int, } } - appendForGroup(group, entries, putCacheCallback) + appendForGroup(group, entries, requestLocal, putCacheCallback) case None => val commitStatus = offsetMetadata.map { case (topicPartition, _) => @@ -526,34 +533,42 @@ class GroupMetadataManager(brokerId: Int, /** * Asynchronously read the partition from the offsets topic and populate the cache */ - def scheduleLoadGroupAndOffsets(offsetsPartition: Int, onGroupLoaded: GroupMetadata => Unit): Unit = { + def scheduleLoadGroupAndOffsets(offsetsPartition: Int, coordinatorEpoch: Int, onGroupLoaded: GroupMetadata => Unit): Unit = { val topicPartition = new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, offsetsPartition) - if (addLoadingPartition(offsetsPartition)) { - info(s"Scheduling loading of offsets and group metadata from $topicPartition") - val startTimeMs = time.milliseconds() - scheduler.schedule(topicPartition.toString, () => loadGroupsAndOffsets(topicPartition, onGroupLoaded, startTimeMs)) - } else { - info(s"Already loading offsets and group metadata from $topicPartition") - } + info(s"Scheduling loading of offsets and group metadata from $topicPartition for epoch $coordinatorEpoch") + val startTimeMs = time.milliseconds() + scheduler.schedule(topicPartition.toString, () => loadGroupsAndOffsets(topicPartition, coordinatorEpoch, onGroupLoaded, startTimeMs)) } - private[group] def loadGroupsAndOffsets(topicPartition: TopicPartition, onGroupLoaded: GroupMetadata => Unit, startTimeMs: java.lang.Long): Unit = { - try { - val schedulerTimeMs = time.milliseconds() - startTimeMs - debug(s"Started loading offsets and group metadata from $topicPartition") - doLoadGroupsAndOffsets(topicPartition, onGroupLoaded) - val endTimeMs = time.milliseconds() - val totalLoadingTimeMs = endTimeMs - startTimeMs - partitionLoadSensor.record(totalLoadingTimeMs.toDouble, endTimeMs, false) - info(s"Finished loading offsets and group metadata from $topicPartition " - + s"in $totalLoadingTimeMs milliseconds, of which $schedulerTimeMs milliseconds" - + s" was spent in the scheduler.") - } catch { - case t: Throwable => error(s"Error loading offsets from $topicPartition", t) - } finally { - inLock(partitionLock) { - ownedPartitions.add(topicPartition.partition) - loadingPartitions.remove(topicPartition.partition) + private[group] def loadGroupsAndOffsets( + topicPartition: TopicPartition, + coordinatorEpoch: Int, + onGroupLoaded: GroupMetadata => Unit, + startTimeMs: java.lang.Long + ): Unit = { + if (!maybeUpdateCoordinatorEpoch(topicPartition.partition, Some(coordinatorEpoch))) { + info(s"Not loading offsets and group metadata for $topicPartition " + + s"in epoch $coordinatorEpoch since current epoch is ${epochForPartitionId.get(topicPartition.partition)}") + } else if (!addLoadingPartition(topicPartition.partition)) { + info(s"Already loading offsets and group metadata from $topicPartition") + } else { + try { + val schedulerTimeMs = time.milliseconds() - startTimeMs + debug(s"Started loading offsets and group metadata from $topicPartition for epoch $coordinatorEpoch") + doLoadGroupsAndOffsets(topicPartition, onGroupLoaded) + val endTimeMs = time.milliseconds() + val totalLoadingTimeMs = endTimeMs - startTimeMs + partitionLoadSensor.record(totalLoadingTimeMs.toDouble, endTimeMs, false) + info(s"Finished loading offsets and group metadata from $topicPartition " + + s"in $totalLoadingTimeMs milliseconds for epoch $coordinatorEpoch, of which " + + s"$schedulerTimeMs milliseconds was spent in the scheduler.") + } catch { + case t: Throwable => error(s"Error loading offsets from $topicPartition", t) + } finally { + inLock(partitionLock) { + ownedPartitions.add(topicPartition.partition) + loadingPartitions.remove(topicPartition.partition) + } } } } @@ -747,20 +762,28 @@ class GroupMetadataManager(brokerId: Int, * @param offsetsPartition Groups belonging to this partition of the offsets topic will be deleted from the cache. */ def removeGroupsForPartition(offsetsPartition: Int, + coordinatorEpoch: Option[Int], onGroupUnloaded: GroupMetadata => Unit): Unit = { val topicPartition = new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, offsetsPartition) info(s"Scheduling unloading of offsets and group metadata from $topicPartition") - scheduler.schedule(topicPartition.toString, () => removeGroupsAndOffsets()) + scheduler.schedule(topicPartition.toString, () => removeGroupsAndOffsets(topicPartition, coordinatorEpoch, onGroupUnloaded)) + } - def removeGroupsAndOffsets(): Unit = { + private [group] def removeGroupsAndOffsets(topicPartition: TopicPartition, + coordinatorEpoch: Option[Int], + onGroupUnloaded: GroupMetadata => Unit): Unit = { + val offsetsPartition = topicPartition.partition + if (maybeUpdateCoordinatorEpoch(offsetsPartition, coordinatorEpoch)) { var numOffsetsRemoved = 0 var numGroupsRemoved = 0 - debug(s"Started unloading offsets and group metadata for $topicPartition") + debug(s"Started unloading offsets and group metadata for $topicPartition for " + + s"coordinator epoch $coordinatorEpoch") inLock(partitionLock) { // we need to guard the group removal in cache in the loading partition lock // to prevent coordinator's check-and-get-group race condition ownedPartitions.remove(offsetsPartition) + loadingPartitions.remove(offsetsPartition) for (group <- groupMetadataCache.values) { if (partitionFor(group.groupId) == offsetsPartition) { @@ -772,18 +795,40 @@ class GroupMetadataManager(brokerId: Int, } } } - - info(s"Finished unloading $topicPartition. Removed $numOffsetsRemoved cached offsets " + - s"and $numGroupsRemoved cached groups.") + info(s"Finished unloading $topicPartition for coordinator epoch $coordinatorEpoch. " + + s"Removed $numOffsetsRemoved cached offsets and $numGroupsRemoved cached groups.") + } else { + info(s"Not removing offsets and group metadata for $topicPartition " + + s"in epoch $coordinatorEpoch since current epoch is ${epochForPartitionId.get(topicPartition.partition)}") } } + /** + * Update the cached coordinator epoch if the new value is larger than the old value. + * @return true if `epochOpt` is either empty or contains a value greater than or equal to the current epoch + */ + private def maybeUpdateCoordinatorEpoch( + partitionId: Int, + epochOpt: Option[Int] + ): Boolean = { + val updatedEpoch = epochForPartitionId.compute(partitionId, (_, currentEpoch) => { + if (currentEpoch == null) { + epochOpt.map(Int.box).orNull + } else { + epochOpt match { + case Some(epoch) if epoch > currentEpoch => epoch + case _ => currentEpoch + } + } + }) + epochOpt.forall(_ == updatedEpoch) + } + // visible for testing private[group] def cleanupGroupMetadata(): Unit = { val currentTimestamp = time.milliseconds() - val numOffsetsRemoved = cleanupGroupMetadata(groupMetadataCache.values, group => { - group.removeExpiredOffsets(currentTimestamp, config.offsetsRetentionMs) - }) + val numOffsetsRemoved = cleanupGroupMetadata(groupMetadataCache.values, RequestLocal.NoCaching, + _.removeExpiredOffsets(currentTimestamp, config.offsetsRetentionMs)) offsetExpiredSensor.record(numOffsetsRemoved) if (numOffsetsRemoved > 0) info(s"Removed $numOffsetsRemoved expired offsets in ${time.milliseconds() - currentTimestamp} milliseconds.") @@ -796,7 +841,8 @@ class GroupMetadataManager(brokerId: Int, * a group lock is held, therefore there is no need for the caller to also obtain a group lock. * @return The cumulative number of offsets removed */ - def cleanupGroupMetadata(groups: Iterable[GroupMetadata], selector: GroupMetadata => Map[TopicPartition, OffsetAndMetadata]): Int = { + def cleanupGroupMetadata(groups: Iterable[GroupMetadata], requestLocal: RequestLocal, + selector: GroupMetadata => Map[TopicPartition, OffsetAndMetadata]): Int = { var offsetsRemoved = 0 groups.foreach { group => @@ -843,7 +889,8 @@ class GroupMetadataManager(brokerId: Int, // do not need to require acks since even if the tombstone is lost, // it will be appended again in the next purge cycle val records = MemoryRecords.withRecords(magicValue, 0L, compressionType, timestampType, tombstones.toArray: _*) - partition.appendRecordsToLeader(records, origin = AppendOrigin.Coordinator, requiredAcks = 0) + partition.appendRecordsToLeader(records, origin = AppendOrigin.Coordinator, requiredAcks = 0, + requestLocal = requestLocal) offsetsRemoved += removedOffsets.size trace(s"Successfully appended ${tombstones.size} tombstones to $appendPartition for expired/deleted " + @@ -961,7 +1008,11 @@ class GroupMetadataManager(brokerId: Int, */ private[group] def addLoadingPartition(partition: Int): Boolean = { inLock(partitionLock) { - loadingPartitions.add(partition) + if (ownedPartitions.contains(partition)) { + false + } else { + loadingPartitions.add(partition) + } } } diff --git a/core/src/main/scala/kafka/coordinator/transaction/TransactionCoordinator.scala b/core/src/main/scala/kafka/coordinator/transaction/TransactionCoordinator.scala index 543e9c85c36d5..78983c16cbf67 100644 --- a/core/src/main/scala/kafka/coordinator/transaction/TransactionCoordinator.scala +++ b/core/src/main/scala/kafka/coordinator/transaction/TransactionCoordinator.scala @@ -18,8 +18,7 @@ package kafka.coordinator.transaction import java.util.Properties import java.util.concurrent.atomic.AtomicBoolean - -import kafka.server.{KafkaConfig, MetadataCache, ReplicaManager} +import kafka.server.{KafkaConfig, MetadataCache, ReplicaManager, RequestLocal} import kafka.utils.{Logging, Scheduler} import org.apache.kafka.common.TopicPartition import org.apache.kafka.common.internals.Topic @@ -104,7 +103,8 @@ class TransactionCoordinator(brokerId: Int, def handleInitProducerId(transactionalId: String, transactionTimeoutMs: Int, expectedProducerIdAndEpoch: Option[ProducerIdAndEpoch], - responseCallback: InitProducerIdCallback): Unit = { + responseCallback: InitProducerIdCallback, + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { if (transactionalId == null) { // if the transactional id is null, then always blindly accept the request @@ -167,7 +167,8 @@ class TransactionCoordinator(brokerId: Int, newMetadata.producerEpoch, TransactionResult.ABORT, isFromClient = false, - sendRetriableErrorCallback) + sendRetriableErrorCallback, + requestLocal) } else { def sendPidResponseCallback(error: Errors): Unit = { if (error == Errors.NONE) { @@ -181,7 +182,8 @@ class TransactionCoordinator(brokerId: Int, } } - txnManager.appendTransactionToLog(transactionalId, coordinatorEpoch, newMetadata, sendPidResponseCallback) + txnManager.appendTransactionToLog(transactionalId, coordinatorEpoch, newMetadata, + sendPidResponseCallback, requestLocal = requestLocal) } } } @@ -320,7 +322,8 @@ class TransactionCoordinator(brokerId: Int, producerId: Long, producerEpoch: Short, partitions: collection.Set[TopicPartition], - responseCallback: AddPartitionsCallback): Unit = { + responseCallback: AddPartitionsCallback, + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { if (transactionalId == null || transactionalId.isEmpty) { debug(s"Returning ${Errors.INVALID_REQUEST} error code to client for $transactionalId's AddPartitions request") responseCallback(Errors.INVALID_REQUEST) @@ -360,7 +363,8 @@ class TransactionCoordinator(brokerId: Int, responseCallback(err) case Right((coordinatorEpoch, newMetadata)) => - txnManager.appendTransactionToLog(transactionalId, coordinatorEpoch, newMetadata, responseCallback) + txnManager.appendTransactionToLog(transactionalId, coordinatorEpoch, newMetadata, + responseCallback, requestLocal = requestLocal) } } } @@ -413,13 +417,15 @@ class TransactionCoordinator(brokerId: Int, producerId: Long, producerEpoch: Short, txnMarkerResult: TransactionResult, - responseCallback: EndTxnCallback): Unit = { + responseCallback: EndTxnCallback, + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { endTransaction(transactionalId, producerId, producerEpoch, txnMarkerResult, isFromClient = true, - responseCallback) + responseCallback, + requestLocal) } private def endTransaction(transactionalId: String, @@ -427,7 +433,8 @@ class TransactionCoordinator(brokerId: Int, producerEpoch: Short, txnMarkerResult: TransactionResult, isFromClient: Boolean, - responseCallback: EndTxnCallback): Unit = { + responseCallback: EndTxnCallback, + requestLocal: RequestLocal): Unit = { var isEpochFence = false if (transactionalId == null || transactionalId.isEmpty) responseCallback(Errors.INVALID_REQUEST) @@ -586,7 +593,8 @@ class TransactionCoordinator(brokerId: Int, } } - txnManager.appendTransactionToLog(transactionalId, coordinatorEpoch, newMetadata, sendTxnMarkersCallback) + txnManager.appendTransactionToLog(transactionalId, coordinatorEpoch, newMetadata, + sendTxnMarkersCallback, requestLocal = requestLocal) } } } @@ -643,7 +651,8 @@ class TransactionCoordinator(brokerId: Int, txnTransitMetadata.producerEpoch, TransactionResult.ABORT, isFromClient = false, - onComplete(txnIdAndPidEpoch)) + onComplete(txnIdAndPidEpoch), + RequestLocal.NoCaching) } } } diff --git a/core/src/main/scala/kafka/coordinator/transaction/TransactionMarkerChannelManager.scala b/core/src/main/scala/kafka/coordinator/transaction/TransactionMarkerChannelManager.scala index 5e22fb7340364..62c70d91121db 100644 --- a/core/src/main/scala/kafka/coordinator/transaction/TransactionMarkerChannelManager.scala +++ b/core/src/main/scala/kafka/coordinator/transaction/TransactionMarkerChannelManager.scala @@ -19,11 +19,10 @@ package kafka.coordinator.transaction import java.util import java.util.concurrent.{BlockingQueue, ConcurrentHashMap, LinkedBlockingQueue} - import kafka.api.KAFKA_2_8_IV0 import kafka.common.{InterBrokerSendThread, RequestAndCompletionHandler} import kafka.metrics.KafkaMetricsGroup -import kafka.server.{KafkaConfig, MetadataCache} +import kafka.server.{KafkaConfig, MetadataCache, RequestLocal} import kafka.utils.Implicits._ import kafka.utils.{CoreUtils, Logging} import org.apache.kafka.clients._ @@ -330,8 +329,8 @@ class TransactionMarkerChannelManager( throw new IllegalStateException(errorMsg) } - txnStateManager.appendTransactionToLog(txnLogAppend.transactionalId, txnLogAppend.coordinatorEpoch, txnLogAppend.newMetadata, appendCallback, - _ == Errors.COORDINATOR_NOT_AVAILABLE) + txnStateManager.appendTransactionToLog(txnLogAppend.transactionalId, txnLogAppend.coordinatorEpoch, + txnLogAppend.newMetadata, appendCallback, _ == Errors.COORDINATOR_NOT_AVAILABLE, RequestLocal.NoCaching) } def addTxnMarkersToBrokerQueue(transactionalId: String, diff --git a/core/src/main/scala/kafka/coordinator/transaction/TransactionStateManager.scala b/core/src/main/scala/kafka/coordinator/transaction/TransactionStateManager.scala index 61fad952dc44f..25580f27a6b74 100644 --- a/core/src/main/scala/kafka/coordinator/transaction/TransactionStateManager.scala +++ b/core/src/main/scala/kafka/coordinator/transaction/TransactionStateManager.scala @@ -21,10 +21,9 @@ import java.util.Properties import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.locks.ReentrantReadWriteLock - import kafka.log.{AppendOrigin, LogConfig} import kafka.message.UncompressedCodec -import kafka.server.{Defaults, FetchLogEnd, ReplicaManager} +import kafka.server.{Defaults, FetchLogEnd, ReplicaManager, RequestLocal} import kafka.utils.CoreUtils.{inReadLock, inWriteLock} import kafka.utils.{Logging, Pool, Scheduler} import kafka.utils.Implicits._ @@ -209,7 +208,8 @@ class TransactionStateManager(brokerId: Int, internalTopicsAllowed = true, origin = AppendOrigin.Coordinator, recordsPerPartition, - removeFromCacheCallback) + removeFromCacheCallback, + requestLocal = RequestLocal.NoCaching) } }, delay = config.removeExpiredTransactionalIdsIntervalMs, period = config.removeExpiredTransactionalIdsIntervalMs) @@ -526,7 +526,8 @@ class TransactionStateManager(brokerId: Int, coordinatorEpoch: Int, newMetadata: TxnTransitMetadata, responseCallback: Errors => Unit, - retryOnError: Errors => Boolean = _ => false): Unit = { + retryOnError: Errors => Boolean = _ => false, + requestLocal: RequestLocal): Unit = { // generate the message for this transaction metadata val keyBytes = TransactionLog.keyToBytes(transactionalId) @@ -679,7 +680,8 @@ class TransactionStateManager(brokerId: Int, internalTopicsAllowed = true, origin = AppendOrigin.Coordinator, recordsPerPartition, - updateCacheCallback) + updateCacheCallback, + requestLocal = requestLocal) trace(s"Appending new metadata $newMetadata for transaction id $transactionalId with coordinator epoch $coordinatorEpoch to the local transaction log") } diff --git a/core/src/main/scala/kafka/log/Log.scala b/core/src/main/scala/kafka/log/Log.scala index 82e083f0c766a..b49bfb46dbdff 100644 --- a/core/src/main/scala/kafka/log/Log.scala +++ b/core/src/main/scala/kafka/log/Log.scala @@ -18,15 +18,12 @@ package kafka.log import java.io.{File, IOException} -import java.lang.{Long => JLong} import java.nio.file.Files import java.text.NumberFormat -import java.util.Map.{Entry => JEntry} import java.util.Optional import java.util.concurrent.atomic._ import java.util.concurrent.TimeUnit import java.util.regex.Pattern - import kafka.api.{ApiVersion, KAFKA_0_10_0_IV0} import kafka.common.{LongRef, OffsetsOutOfOrderException, UnexpectedAppendOffsetException} import kafka.log.AppendOrigin.RaftLeader @@ -34,7 +31,7 @@ import kafka.message.{BrokerCompressionCodec, CompressionCodec, NoCompressionCod import kafka.metrics.KafkaMetricsGroup import kafka.server.checkpoints.LeaderEpochCheckpointFile import kafka.server.epoch.LeaderEpochFileCache -import kafka.server.{BrokerTopicStats, FetchDataInfo, FetchHighWatermark, FetchIsolation, FetchLogEnd, FetchTxnCommitted, LogDirFailureChannel, LogOffsetMetadata, OffsetAndEpoch, PartitionMetadataFile} +import kafka.server.{BrokerTopicStats, FetchDataInfo, FetchHighWatermark, FetchIsolation, FetchLogEnd, FetchTxnCommitted, LogDirFailureChannel, LogOffsetMetadata, OffsetAndEpoch, PartitionMetadataFile, RequestLocal} import kafka.utils._ import org.apache.kafka.common.errors._ import org.apache.kafka.common.message.{DescribeProducersResponseData, FetchResponseData} @@ -272,7 +269,7 @@ class Log(@volatile private var _dir: File, @volatile var leaderEpochCache: Option[LeaderEpochFileCache], val producerStateManager: ProducerStateManager, logDirFailureChannel: LogDirFailureChannel, - @volatile var topicId: Option[Uuid], + @volatile private var _topicId: Option[Uuid], val keepPartitionMetadataFile: Boolean) extends Logging with KafkaMetricsGroup { import kafka.log.Log._ @@ -326,18 +323,24 @@ class Log(@volatile private var _dir: File, if (partitionMetadataFile.exists()) { if (keepPartitionMetadataFile) { val fileTopicId = partitionMetadataFile.read().topicId - if (topicId.isDefined && !topicId.contains(fileTopicId)) + if (_topicId.isDefined && !_topicId.contains(fileTopicId)) throw new InconsistentTopicIdException(s"Tried to assign topic ID $topicId to log for topic partition $topicPartition," + s"but log already contained topic ID $fileTopicId") - topicId = Some(fileTopicId) + _topicId = Some(fileTopicId) } else { - partitionMetadataFile.delete() + try partitionMetadataFile.delete() + catch { + case e: IOException => + error(s"Error while trying to delete partition metadata file ${partitionMetadataFile}", e) + } } } else if (keepPartitionMetadataFile) { - topicId.foreach(partitionMetadataFile.write) + _topicId.foreach(partitionMetadataFile.write) } } + def topicId: Option[Uuid] = _topicId + def dir: File = _dir def parentDir: String = _parentDir @@ -553,7 +556,7 @@ class Log(@volatile private var _dir: File, /** Only used for ZK clusters when we update and start using topic IDs on existing topics */ def assignTopicId(topicId: Uuid): Unit = { partitionMetadataFile.write(topicId) - this.topicId = Some(topicId) + _topicId = Some(topicId) } private def initializeLeaderEpochCache(): Unit = lock synchronized { @@ -689,15 +692,17 @@ class Log(@volatile private var _dir: File, * @param records The records to append * @param origin Declares the origin of the append which affects required validations * @param interBrokerProtocolVersion Inter-broker message protocol version + * @param requestLocal request local instance * @throws KafkaStorageException If the append fails due to an I/O error. * @return Information about the appended messages including the first and last offset. */ def appendAsLeader(records: MemoryRecords, leaderEpoch: Int, origin: AppendOrigin = AppendOrigin.Client, - interBrokerProtocolVersion: ApiVersion = ApiVersion.latestVersion): LogAppendInfo = { + interBrokerProtocolVersion: ApiVersion = ApiVersion.latestVersion, + requestLocal: RequestLocal = RequestLocal.NoCaching): LogAppendInfo = { val validateAndAssignOffsets = origin != AppendOrigin.RaftLeader - append(records, origin, interBrokerProtocolVersion, validateAndAssignOffsets, leaderEpoch, ignoreRecordSize = false) + append(records, origin, interBrokerProtocolVersion, validateAndAssignOffsets, leaderEpoch, Some(requestLocal), ignoreRecordSize = false) } /** @@ -713,6 +718,7 @@ class Log(@volatile private var _dir: File, interBrokerProtocolVersion = ApiVersion.latestVersion, validateAndAssignOffsets = false, leaderEpoch = -1, + None, // disable to check the validation of record size since the record is already accepted by leader. ignoreRecordSize = true) } @@ -728,6 +734,7 @@ class Log(@volatile private var _dir: File, * @param interBrokerProtocolVersion Inter-broker message protocol version * @param validateAndAssignOffsets Should the log assign offsets to this message set or blindly apply what it is given * @param leaderEpoch The partition's leader epoch which will be applied to messages when offsets are assigned on the leader + * @param requestLocal The request local instance if assignOffsets is true * @param ignoreRecordSize true to skip validation of record size. * @throws KafkaStorageException If the append fails due to an I/O error. * @throws OffsetsOutOfOrderException If out of order offsets found in 'records' @@ -739,6 +746,7 @@ class Log(@volatile private var _dir: File, interBrokerProtocolVersion: ApiVersion, validateAndAssignOffsets: Boolean, leaderEpoch: Int, + requestLocal: Option[RequestLocal], ignoreRecordSize: Boolean): LogAppendInfo = { val appendInfo = analyzeAndValidateRecords(records, origin, ignoreRecordSize, leaderEpoch) @@ -774,7 +782,9 @@ class Log(@volatile private var _dir: File, leaderEpoch, origin, interBrokerProtocolVersion, - brokerTopicStats) + brokerTopicStats, + requestLocal.getOrElse(throw new IllegalArgumentException( + "requestLocal should be defined if assignOffsets is true"))) } catch { case e: IOException => throw new KafkaException(s"Error validating messages while appending to log $name", e) @@ -1181,10 +1191,10 @@ class Log(@volatile private var _dir: File, // We create the local variables to avoid race conditions with updates to the log. val endOffsetMetadata = nextOffsetMetadata val endOffset = endOffsetMetadata.messageOffset - var segmentEntryOpt = segments.floorEntry(startOffset) + var segmentOpt = segments.floorSegment(startOffset) // return error on attempt to read beyond the log end offset or read below log start offset - if (startOffset > endOffset || segmentEntryOpt.isEmpty || startOffset < logStartOffset) + if (startOffset > endOffset || segmentOpt.isEmpty || startOffset < logStartOffset) throw new OffsetOutOfRangeException(s"Received request for offset $startOffset for partition $topicPartition, " + s"but we only have log segments in the range $logStartOffset to $endOffset.") @@ -1202,12 +1212,10 @@ class Log(@volatile private var _dir: File, // Do the read on the segment with a base offset less than the target offset // but if that segment doesn't contain any messages with an offset greater than that // continue to read from successive segments until we get some messages or we reach the end of the log - var done = segmentEntryOpt.isEmpty var fetchDataInfo: FetchDataInfo = null - while (!done) { - val segmentEntry = segmentEntryOpt.get - val baseOffset = segmentEntry.getKey - val segment = segmentEntry.getValue + while (fetchDataInfo == null && segmentOpt.isDefined) { + val segment = segmentOpt.get + val baseOffset = segment.baseOffset val maxPosition = // Use the max offset position if it is on this segment; otherwise, the segment size is the limit. @@ -1217,10 +1225,8 @@ class Log(@volatile private var _dir: File, fetchDataInfo = segment.read(startOffset, maxLength, maxPosition, minOneMessage) if (fetchDataInfo != null) { if (includeAbortedTxns) - fetchDataInfo = addAbortedTransactions(startOffset, segmentEntry, fetchDataInfo) - } else segmentEntryOpt = segments.higherEntry(baseOffset) - - done = fetchDataInfo != null || segmentEntryOpt.isEmpty + fetchDataInfo = addAbortedTransactions(startOffset, segment, fetchDataInfo) + } else segmentOpt = segments.higherSegment(baseOffset) } if (fetchDataInfo != null) fetchDataInfo @@ -1235,25 +1241,25 @@ class Log(@volatile private var _dir: File, } private[log] def collectAbortedTransactions(startOffset: Long, upperBoundOffset: Long): List[AbortedTxn] = { - val segmentEntryOpt = segments.floorEntry(startOffset) + val segmentEntry = segments.floorSegment(startOffset) val allAbortedTxns = ListBuffer.empty[AbortedTxn] def accumulator(abortedTxns: List[AbortedTxn]): Unit = allAbortedTxns ++= abortedTxns - collectAbortedTransactions(logStartOffset, upperBoundOffset, segmentEntryOpt.get, accumulator) + segmentEntry.foreach(segment => collectAbortedTransactions(logStartOffset, upperBoundOffset, segment, accumulator)) allAbortedTxns.toList } - private def addAbortedTransactions(startOffset: Long, segmentEntry: JEntry[JLong, LogSegment], + private def addAbortedTransactions(startOffset: Long, segment: LogSegment, fetchInfo: FetchDataInfo): FetchDataInfo = { val fetchSize = fetchInfo.records.sizeInBytes val startOffsetPosition = OffsetPosition(fetchInfo.fetchOffsetMetadata.messageOffset, fetchInfo.fetchOffsetMetadata.relativePositionInSegment) - val upperBoundOffset = segmentEntry.getValue.fetchUpperBoundOffset(startOffsetPosition, fetchSize).getOrElse { - segments.higherSegment(segmentEntry.getKey).map(_.baseOffset).getOrElse(logEndOffset) + val upperBoundOffset = segment.fetchUpperBoundOffset(startOffsetPosition, fetchSize).getOrElse { + segments.higherSegment(segment.baseOffset).map(_.baseOffset).getOrElse(logEndOffset) } val abortedTransactions = ListBuffer.empty[FetchResponseData.AbortedTransaction] def accumulator(abortedTxns: List[AbortedTxn]): Unit = abortedTransactions ++= abortedTxns.map(_.asAbortedTransaction) - collectAbortedTransactions(startOffset, upperBoundOffset, segmentEntry, accumulator) + collectAbortedTransactions(startOffset, upperBoundOffset, segment, accumulator) FetchDataInfo(fetchOffsetMetadata = fetchInfo.fetchOffsetMetadata, records = fetchInfo.records, @@ -1262,17 +1268,17 @@ class Log(@volatile private var _dir: File, } private def collectAbortedTransactions(startOffset: Long, upperBoundOffset: Long, - startingSegmentEntry: JEntry[JLong, LogSegment], + startingSegment: LogSegment, accumulator: List[AbortedTxn] => Unit): Unit = { - var segmentEntryOpt = Option(startingSegmentEntry) + val higherSegments = segments.higherSegments(startingSegment.baseOffset).iterator + var segmentEntryOpt = Option(startingSegment) while (segmentEntryOpt.isDefined) { - val baseOffset = segmentEntryOpt.get.getKey - val segment = segmentEntryOpt.get.getValue + val segment = segmentEntryOpt.get val searchResult = segment.collectAbortedTxns(startOffset, upperBoundOffset) accumulator(searchResult.abortedTransactions) if (searchResult.isComplete) return - segmentEntryOpt = segments.higherEntry(baseOffset) + segmentEntryOpt = nextOption(higherSegments) } } @@ -1438,23 +1444,23 @@ class Log(@volatile private var _dir: File, Seq.empty } else { val deletable = ArrayBuffer.empty[LogSegment] - var segmentEntryOpt = segments.firstEntry - while (segmentEntryOpt.isDefined) { - val segmentEntry = segmentEntryOpt.get - val segment = segmentEntry.getValue - val nextSegmentEntryOpt = segments.higherEntry(segmentEntry.getKey) - val (nextSegment, upperBoundOffset, isLastSegmentAndEmpty) = - nextSegmentEntryOpt.map { - entry => (entry.getValue, entry.getValue.baseOffset, false) + val segmentsIterator = segments.values.iterator + var segmentOpt = nextOption(segmentsIterator) + while (segmentOpt.isDefined) { + val segment = segmentOpt.get + val nextSegmentOpt = nextOption(segmentsIterator) + val (upperBoundOffset: Long, isLastSegmentAndEmpty: Boolean) = + nextSegmentOpt.map { + nextSegment => (nextSegment.baseOffset, false) }.getOrElse { - (null, logEndOffset, segment.size == 0) + (logEndOffset, segment.size == 0) } - if (highWatermark >= upperBoundOffset && predicate(segment, Option(nextSegment)) && !isLastSegmentAndEmpty) { + if (highWatermark >= upperBoundOffset && predicate(segment, nextSegmentOpt) && !isLastSegmentAndEmpty) { deletable += segment - segmentEntryOpt = nextSegmentEntryOpt + segmentOpt = nextSegmentOpt } else { - segmentEntryOpt = Option.empty + segmentOpt = Option.empty } } deletable @@ -2450,11 +2456,11 @@ object Log extends Logging { time: Time, reloadFromCleanShutdown: Boolean, logPrefix: String): Unit = { - val allSegments = segments.values val offsetsToSnapshot = - if (allSegments.nonEmpty) { - val nextLatestSegmentBaseOffset = segments.lowerSegment(allSegments.last.baseOffset).map(_.baseOffset) - Seq(nextLatestSegmentBaseOffset, Some(allSegments.last.baseOffset), Some(lastOffset)) + if (segments.nonEmpty) { + val lastSegmentBaseOffset = segments.lastSegment.get.baseOffset + val nextLatestSegmentBaseOffset = segments.lowerSegment(lastSegmentBaseOffset).map(_.baseOffset) + Seq(nextLatestSegmentBaseOffset, Some(lastSegmentBaseOffset), Some(lastOffset)) } else { Seq(Some(lastOffset)) } @@ -2609,6 +2615,21 @@ object Log extends Logging { throw e } } + + /** + * Wraps the value of iterator.next() in an option. + * Note: this facility is a part of the Iterator class starting from scala v2.13. + * + * @param iterator + * @tparam T the type of object held within the iterator + * @return Some(iterator.next) if a next element exists, None otherwise. + */ + private def nextOption[T](iterator: Iterator[T]): Option[T] = { + if (iterator.hasNext) + Some(iterator.next()) + else + None + } } object LogMetricNames { diff --git a/core/src/main/scala/kafka/log/LogLoader.scala b/core/src/main/scala/kafka/log/LogLoader.scala index ef9ba000d6ffb..0a9222e70386f 100644 --- a/core/src/main/scala/kafka/log/LogLoader.scala +++ b/core/src/main/scala/kafka/log/LogLoader.scala @@ -373,7 +373,7 @@ object LogLoader extends Logging { params.config, time = params.time, fileSuffix = Log.SwapFileSuffix) - info(s"Found log file ${swapFile.getPath} from interrupted swap operation, repairing.") + info(s"${params.logIdentifier}Found log file ${swapFile.getPath} from interrupted swap operation, repairing.") recoverSegment(swapSegment, params) // We create swap files for two cases: @@ -425,8 +425,9 @@ object LogLoader extends Logging { if (logEndOffset >= params.logStartOffsetCheckpoint) Some(logEndOffset) else { - warn(s"Deleting all segments because logEndOffset ($logEndOffset) is smaller than logStartOffset ${params.logStartOffsetCheckpoint}. " + - "This could happen if segment files were deleted from the file system.") + warn(s"${params.logIdentifier}Deleting all segments because logEndOffset ($logEndOffset) " + + s" smaller than logStartOffset ${params.logStartOffsetCheckpoint}." + + " This could happen if segment files were deleted from the file system.") removeAndDeleteSegmentsAsync(params.segments.values, params) params.leaderEpochCache.foreach(_.clearAndFlush()) params.producerStateManager.truncateFullyAndStartAt(params.logStartOffsetCheckpoint) @@ -514,7 +515,7 @@ object LogLoader extends Logging { // materialization of the iterator here, so that results of the iteration remain valid and // deterministic. val toDelete = segmentsToDelete.toList - info(s"Deleting segments as part of log recovery: ${toDelete.mkString(",")}") + info(s"${params.logIdentifier}Deleting segments as part of log recovery: ${toDelete.mkString(",")}") toDelete.foreach { segment => params.segments.remove(segment.baseOffset) } diff --git a/core/src/main/scala/kafka/log/LogSegments.scala b/core/src/main/scala/kafka/log/LogSegments.scala index d9e564ed4b13d..d6886d7fdef13 100644 --- a/core/src/main/scala/kafka/log/LogSegments.scala +++ b/core/src/main/scala/kafka/log/LogSegments.scala @@ -17,7 +17,6 @@ package kafka.log import java.io.File -import java.lang.{Long => JLong} import java.util.Map import java.util.concurrent.{ConcurrentNavigableMap, ConcurrentSkipListMap} @@ -36,7 +35,7 @@ import scala.jdk.CollectionConverters._ class LogSegments(topicPartition: TopicPartition) { /* the segments of the log with key being LogSegment base offset and value being a LogSegment */ - private val segments: ConcurrentNavigableMap[java.lang.Long, LogSegment] = new ConcurrentSkipListMap[java.lang.Long, LogSegment] + private val segments: ConcurrentNavigableMap[Long, LogSegment] = new ConcurrentSkipListMap[Long, LogSegment] /** * @return true if the segments are empty, false otherwise. @@ -157,7 +156,7 @@ class LogSegments(topicPartition: TopicPartition) { * if it exists. */ @threadsafe - def floorEntry(offset: Long): Option[Map.Entry[JLong, LogSegment]] = Option(segments.floorEntry(offset)) + private def floorEntry(offset: Long): Option[Map.Entry[Long, LogSegment]] = Option(segments.floorEntry(offset)) /** * @return the log segment with the greatest offset less than or equal to the given offset, @@ -171,7 +170,7 @@ class LogSegments(topicPartition: TopicPartition) { * if it exists. */ @threadsafe - def lowerEntry(offset: Long): Option[Map.Entry[JLong, LogSegment]] = Option(segments.lowerEntry(offset)) + private def lowerEntry(offset: Long): Option[Map.Entry[Long, LogSegment]] = Option(segments.lowerEntry(offset)) /** * @return the log segment with the greatest offset strictly less than the given offset, @@ -185,7 +184,7 @@ class LogSegments(topicPartition: TopicPartition) { * if it exists. */ @threadsafe - def higherEntry(offset: Long): Option[Map.Entry[JLong, LogSegment]] = Option(segments.higherEntry(offset)) + def higherEntry(offset: Long): Option[Map.Entry[Long, LogSegment]] = Option(segments.higherEntry(offset)) /** * @return the log segment with the smallest offset strictly greater than the given offset, @@ -198,7 +197,7 @@ class LogSegments(topicPartition: TopicPartition) { * @return the entry associated with the smallest offset, if it exists. */ @threadsafe - def firstEntry: Option[Map.Entry[JLong, LogSegment]] = Option(segments.firstEntry) + def firstEntry: Option[Map.Entry[Long, LogSegment]] = Option(segments.firstEntry) /** * @return the log segment associated with the smallest offset, if it exists. @@ -210,11 +209,23 @@ class LogSegments(topicPartition: TopicPartition) { * @return the entry associated with the greatest offset, if it exists. */ @threadsafe - def lastEntry: Option[Map.Entry[JLong, LogSegment]] = Option(segments.lastEntry) + def lastEntry: Option[Map.Entry[Long, LogSegment]] = Option(segments.lastEntry) /** * @return the log segment with the greatest offset, if it exists. */ @threadsafe def lastSegment: Option[LogSegment] = lastEntry.map(_.getValue) + + /** + * @return an iterable with log segments ordered from lowest base offset to highest, + * each segment returned has a base offset strictly greater than the provided baseOffset. + */ + def higherSegments(baseOffset: Long): Iterable[LogSegment] = { + val view = + Option(segments.higherKey(baseOffset)).map { + higherOffset => segments.tailMap(higherOffset, true) + }.getOrElse(collection.immutable.Map[Long, LogSegment]().asJava) + view.values.asScala + } } diff --git a/core/src/main/scala/kafka/log/LogValidator.scala b/core/src/main/scala/kafka/log/LogValidator.scala index 056be10be181c..925c60294a6af 100644 --- a/core/src/main/scala/kafka/log/LogValidator.scala +++ b/core/src/main/scala/kafka/log/LogValidator.scala @@ -20,7 +20,7 @@ import java.nio.ByteBuffer import kafka.api.{ApiVersion, KAFKA_2_1_IV0} import kafka.common.{LongRef, RecordValidationException} import kafka.message.{CompressionCodec, NoCompressionCodec, ZStdCompressionCodec} -import kafka.server.BrokerTopicStats +import kafka.server.{BrokerTopicStats, RequestLocal} import kafka.utils.Logging import org.apache.kafka.common.errors.{CorruptRecordException, InvalidTimestampException, UnsupportedCompressionTypeException, UnsupportedForMessageFormatException} import org.apache.kafka.common.record.{AbstractRecords, CompressionType, MemoryRecords, Record, RecordBatch, RecordConversionStats, TimestampType} @@ -28,7 +28,7 @@ import org.apache.kafka.common.InvalidRecordException import org.apache.kafka.common.TopicPartition import org.apache.kafka.common.protocol.Errors import org.apache.kafka.common.requests.ProduceResponse.RecordError -import org.apache.kafka.common.utils.{BufferSupplier, Time} +import org.apache.kafka.common.utils.Time import scala.collection.{Seq, mutable} import scala.jdk.CollectionConverters._ @@ -95,7 +95,8 @@ private[log] object LogValidator extends Logging { partitionLeaderEpoch: Int, origin: AppendOrigin, interBrokerProtocolVersion: ApiVersion, - brokerTopicStats: BrokerTopicStats): ValidationAndOffsetAssignResult = { + brokerTopicStats: BrokerTopicStats, + requestLocal: RequestLocal): ValidationAndOffsetAssignResult = { if (sourceCodec == NoCompressionCodec && targetCodec == NoCompressionCodec) { // check the magic value if (!records.hasMatchingMagic(magic)) @@ -106,8 +107,9 @@ private[log] object LogValidator extends Logging { assignOffsetsNonCompressed(records, topicPartition, offsetCounter, now, compactedTopic, timestampType, timestampDiffMaxMs, partitionLeaderEpoch, origin, magic, brokerTopicStats) } else { - validateMessagesAndAssignOffsetsCompressed(records, topicPartition, offsetCounter, time, now, sourceCodec, targetCodec, compactedTopic, - magic, timestampType, timestampDiffMaxMs, partitionLeaderEpoch, origin, interBrokerProtocolVersion, brokerTopicStats) + validateMessagesAndAssignOffsetsCompressed(records, topicPartition, offsetCounter, time, now, sourceCodec, + targetCodec, compactedTopic, magic, timestampType, timestampDiffMaxMs, partitionLeaderEpoch, origin, + interBrokerProtocolVersion, brokerTopicStats, requestLocal) } } @@ -232,6 +234,8 @@ private[log] object LogValidator extends Logging { (first.producerId, first.producerEpoch, first.baseSequence, first.isTransactional) } + // The current implementation of BufferSupplier is naive and works best when the buffer size + // cardinality is low, so don't use it here val newBuffer = ByteBuffer.allocate(sizeInBytesAfterConversion) val builder = MemoryRecords.builder(newBuffer, toMagicValue, CompressionType.NONE, timestampType, offsetCounter.value, now, producerId, producerEpoch, sequence, isTransactional, partitionLeaderEpoch) @@ -290,7 +294,9 @@ private[log] object LogValidator extends Logging { var offsetOfMaxBatchTimestamp = -1L val recordErrors = new ArrayBuffer[ApiRecordError](0) - // this is a hot path and we want to avoid any unnecessary allocations. + // This is a hot path and we want to avoid any unnecessary allocations. + // That said, there is no benefit in using `skipKeyValueIterator` for the uncompressed + // case since we don't do key/value copies in this path (we just slice the ByteBuffer) var batchIndex = 0 batch.forEach { record => validateRecord(batch, topicPartition, record, batchIndex, now, timestampType, @@ -360,7 +366,8 @@ private[log] object LogValidator extends Logging { partitionLeaderEpoch: Int, origin: AppendOrigin, interBrokerProtocolVersion: ApiVersion, - brokerTopicStats: BrokerTopicStats): ValidationAndOffsetAssignResult = { + brokerTopicStats: BrokerTopicStats, + requestLocal: RequestLocal): ValidationAndOffsetAssignResult = { if (targetCodec == ZStdCompressionCodec && interBrokerProtocolVersion < KAFKA_2_1_IV0) throw new UnsupportedCompressionTypeException("Produce requests to inter.broker.protocol.version < 2.1 broker " + @@ -404,9 +411,9 @@ private[log] object LogValidator extends Logging { // if we are on version 2 and beyond, and we know we are going for in place assignment, // then we can optimize the iterator to skip key / value / headers since they would not be used at all val recordsIterator = if (inPlaceAssignment && firstBatch.magic >= RecordBatch.MAGIC_VALUE_V2) - batch.skipKeyValueIterator(BufferSupplier.NO_CACHING) + batch.skipKeyValueIterator(requestLocal.bufferSupplier) else - batch.streamingIterator(BufferSupplier.NO_CACHING) + batch.streamingIterator(requestLocal.bufferSupplier) try { val recordErrors = new ArrayBuffer[ApiRecordError](0) @@ -499,6 +506,8 @@ private[log] object LogValidator extends Logging { val startNanos = time.nanoseconds val estimatedSize = AbstractRecords.estimateSizeInBytes(magic, offsetCounter.value, compressionType, validatedRecords.asJava) + // The current implementation of BufferSupplier is naive and works best when the buffer size + // cardinality is low, so don't use it here val buffer = ByteBuffer.allocate(estimatedSize) val builder = MemoryRecords.builder(buffer, magic, compressionType, timestampType, offsetCounter.value, logAppendTime, producerId, producerEpoch, baseSequence, isTransactional, partitionLeaderEpoch) diff --git a/core/src/main/scala/kafka/raft/KafkaMetadataLog.scala b/core/src/main/scala/kafka/raft/KafkaMetadataLog.scala index 6ac39b7f6cbb5..7b5f83dd53ac7 100644 --- a/core/src/main/scala/kafka/raft/KafkaMetadataLog.scala +++ b/core/src/main/scala/kafka/raft/KafkaMetadataLog.scala @@ -19,10 +19,9 @@ package kafka.raft import java.io.File import java.nio.file.{Files, NoSuchFileException, Path} import java.util.{Optional, Properties} - import kafka.api.ApiVersion import kafka.log.{AppendOrigin, Log, LogConfig, LogOffsetSnapshot, SnapshotGenerated} -import kafka.server.{BrokerTopicStats, FetchHighWatermark, FetchLogEnd, LogDirFailureChannel} +import kafka.server.{BrokerTopicStats, FetchHighWatermark, FetchLogEnd, LogDirFailureChannel, RequestLocal} import kafka.utils.{CoreUtils, Logging, Scheduler} import org.apache.kafka.common.record.{MemoryRecords, Records} import org.apache.kafka.common.utils.Time @@ -76,7 +75,8 @@ final class KafkaMetadataLog private ( handleAndConvertLogAppendInfo( log.appendAsLeader(records.asInstanceOf[MemoryRecords], leaderEpoch = epoch, - origin = AppendOrigin.RaftLeader + origin = AppendOrigin.RaftLeader, + requestLocal = RequestLocal.NoCaching ) ) } diff --git a/core/src/main/scala/kafka/server/BrokerServer.scala b/core/src/main/scala/kafka/server/BrokerServer.scala index bea0c53c1d78f..4c76903173ab3 100644 --- a/core/src/main/scala/kafka/server/BrokerServer.scala +++ b/core/src/main/scala/kafka/server/BrokerServer.scala @@ -42,7 +42,7 @@ import org.apache.kafka.common.security.auth.SecurityProtocol import org.apache.kafka.common.security.scram.internals.ScramMechanism import org.apache.kafka.common.security.token.delegation.internals.DelegationTokenCache import org.apache.kafka.common.utils.{AppInfoParser, LogContext, Time, Utils} -import org.apache.kafka.common.{ClusterResource, Endpoint, KafkaException} +import org.apache.kafka.common.{ClusterResource, Endpoint} import org.apache.kafka.metadata.{BrokerState, VersionRange} import org.apache.kafka.raft.RaftConfig import org.apache.kafka.raft.RaftConfig.AddressSpec @@ -244,11 +244,18 @@ class BrokerServer( // Hardcode Time.SYSTEM for now as some Streams tests fail otherwise, it would be good to fix the underlying issue groupCoordinator = GroupCoordinator(config, replicaManager, Time.SYSTEM, metrics) + val producerIdManagerSupplier = () => ProducerIdManager.rpc( + config.brokerId, + brokerEpochSupplier = () => lifecycleManager.brokerEpoch(), + clientToControllerChannelManager, + config.requestTimeoutMs + ) + // Create transaction coordinator, but don't start it until we've started replica manager. // Hardcode Time.SYSTEM for now as some Streams tests fail otherwise, it would be good to fix the underlying issue transactionCoordinator = TransactionCoordinator(config, replicaManager, new KafkaScheduler(threads = 1, threadNamePrefix = "transaction-log-manager-"), - createTemporaryProducerIdManager, metrics, metadataCache, Time.SYSTEM) + producerIdManagerSupplier, metrics, metadataCache, Time.SYSTEM) autoTopicCreationManager = new DefaultAutoTopicCreationManager( config, Some(clientToControllerChannelManager), None, None, @@ -376,24 +383,6 @@ class BrokerServer( } } - class TemporaryProducerIdManager() extends ProducerIdManager { - val maxProducerIdsPerBrokerEpoch = 1000000 - var currentOffset = -1 - override def generateProducerId(): Long = { - currentOffset = currentOffset + 1 - if (currentOffset >= maxProducerIdsPerBrokerEpoch) { - fatal(s"Exhausted all demo/temporary producerIds as the next one will has extend past the block size of $maxProducerIdsPerBrokerEpoch") - throw new KafkaException("Have exhausted all demo/temporary producerIds.") - } - lifecycleManager.initialCatchUpFuture.get() - lifecycleManager.brokerEpoch() * maxProducerIdsPerBrokerEpoch + currentOffset - } - } - - def createTemporaryProducerIdManager(): ProducerIdManager = { - new TemporaryProducerIdManager() - } - def shutdown(): Unit = { if (!maybeChangeStatus(STARTED, SHUTTING_DOWN)) return try { diff --git a/core/src/main/scala/kafka/server/ControllerApis.scala b/core/src/main/scala/kafka/server/ControllerApis.scala index 47bc19d69553a..96822e8e8810c 100644 --- a/core/src/main/scala/kafka/server/ControllerApis.scala +++ b/core/src/main/scala/kafka/server/ControllerApis.scala @@ -76,7 +76,7 @@ class ControllerApis(val requestChannel: RequestChannel, val requestHelper = new RequestHandlerHelper(requestChannel, quotas, time) private val aclApis = new AclApis(authHelper, authorizer, requestHelper, "controller", config) - override def handle(request: RequestChannel.Request): Unit = { + override def handle(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { try { request.header.apiKey match { case ApiKeys.FETCH => handleFetch(request) @@ -97,9 +97,10 @@ class ControllerApis(val requestChannel: RequestChannel, case ApiKeys.INCREMENTAL_ALTER_CONFIGS => handleIncrementalAlterConfigs(request) case ApiKeys.ALTER_PARTITION_REASSIGNMENTS => handleAlterPartitionReassignments(request) case ApiKeys.LIST_PARTITION_REASSIGNMENTS => handleListPartitionReassignments(request) - case ApiKeys.ENVELOPE => handleEnvelopeRequest(request) + case ApiKeys.ENVELOPE => handleEnvelopeRequest(request, requestLocal) case ApiKeys.SASL_HANDSHAKE => handleSaslHandshakeRequest(request) case ApiKeys.SASL_AUTHENTICATE => handleSaslAuthenticateRequest(request) + case ApiKeys.ALLOCATE_PRODUCER_IDS => handleAllocateProducerIdsRequest(request) case ApiKeys.CREATE_PARTITIONS => handleCreatePartitions(request) case ApiKeys.DESCRIBE_ACLS => aclApis.handleDescribeAcls(request) case ApiKeys.CREATE_ACLS => aclApis.handleCreateAcls(request) @@ -113,12 +114,12 @@ class ControllerApis(val requestChannel: RequestChannel, } } - def handleEnvelopeRequest(request: RequestChannel.Request): Unit = { + def handleEnvelopeRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { if (!authHelper.authorize(request.context, CLUSTER_ACTION, CLUSTER, CLUSTER_NAME)) { requestHelper.sendErrorResponseMaybeThrottle(request, new ClusterAuthorizationException( s"Principal ${request.context.principal} does not have required CLUSTER_ACTION for envelope")) } else { - EnvelopeUtils.handleEnvelopeRequest(request, requestChannel.metrics, handle) + EnvelopeUtils.handleEnvelopeRequest(request, requestChannel.metrics, handle(_, requestLocal)) } } @@ -767,4 +768,20 @@ class ControllerApis(val requestChannel: RequestChannel, requestHelper.sendResponseMaybeThrottle(request, requestThrottleMs => new ListPartitionReassignmentsResponse(response.setThrottleTimeMs(requestThrottleMs))) } + + def handleAllocateProducerIdsRequest(request: RequestChannel.Request): Unit = { + val allocatedProducerIdsRequest = request.body[AllocateProducerIdsRequest] + authHelper.authorizeClusterOperation(request, CLUSTER_ACTION) + controller.allocateProducerIds(allocatedProducerIdsRequest.data) + .whenComplete((results, exception) => { + if (exception != null) { + requestHelper.handleError(request, exception) + } else { + requestHelper.sendResponseMaybeThrottle(request, requestThrottleMs => { + results.setThrottleTimeMs(requestThrottleMs) + new AllocateProducerIdsResponse(results) + }) + } + }) + } } diff --git a/core/src/main/scala/kafka/server/KafkaApis.scala b/core/src/main/scala/kafka/server/KafkaApis.scala index d75e4ae3651d7..bafc0411253b4 100644 --- a/core/src/main/scala/kafka/server/KafkaApis.scala +++ b/core/src/main/scala/kafka/server/KafkaApis.scala @@ -144,7 +144,7 @@ class KafkaApis(val requestChannel: RequestChannel, /** * Top-level method that handles all requests and multiplexes to the right api */ - override def handle(request: RequestChannel.Request): Unit = { + override def handle(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { try { trace(s"Handling request:${request.requestDesc(true)} from connection ${request.context.connectionId};" + s"securityProtocol:${request.context.securityProtocol},principal:${request.context.principal}") @@ -156,21 +156,21 @@ class KafkaApis(val requestChannel: RequestChannel, } request.header.apiKey match { - case ApiKeys.PRODUCE => handleProduceRequest(request) + case ApiKeys.PRODUCE => handleProduceRequest(request, requestLocal) case ApiKeys.FETCH => handleFetchRequest(request) case ApiKeys.LIST_OFFSETS => handleListOffsetRequest(request) case ApiKeys.METADATA => handleTopicMetadataRequest(request) case ApiKeys.LEADER_AND_ISR => handleLeaderAndIsrRequest(request) case ApiKeys.STOP_REPLICA => handleStopReplicaRequest(request) - case ApiKeys.UPDATE_METADATA => handleUpdateMetadataRequest(request) + case ApiKeys.UPDATE_METADATA => handleUpdateMetadataRequest(request, requestLocal) case ApiKeys.CONTROLLED_SHUTDOWN => handleControlledShutdownRequest(request) - case ApiKeys.OFFSET_COMMIT => handleOffsetCommitRequest(request) + case ApiKeys.OFFSET_COMMIT => handleOffsetCommitRequest(request, requestLocal) case ApiKeys.OFFSET_FETCH => handleOffsetFetchRequest(request) case ApiKeys.FIND_COORDINATOR => handleFindCoordinatorRequest(request) - case ApiKeys.JOIN_GROUP => handleJoinGroupRequest(request) + case ApiKeys.JOIN_GROUP => handleJoinGroupRequest(request, requestLocal) case ApiKeys.HEARTBEAT => handleHeartbeatRequest(request) case ApiKeys.LEAVE_GROUP => handleLeaveGroupRequest(request) - case ApiKeys.SYNC_GROUP => handleSyncGroupRequest(request) + case ApiKeys.SYNC_GROUP => handleSyncGroupRequest(request, requestLocal) case ApiKeys.DESCRIBE_GROUPS => handleDescribeGroupRequest(request) case ApiKeys.LIST_GROUPS => handleListGroupsRequest(request) case ApiKeys.SASL_HANDSHAKE => handleSaslHandshakeRequest(request) @@ -178,13 +178,13 @@ class KafkaApis(val requestChannel: RequestChannel, case ApiKeys.CREATE_TOPICS => maybeForwardToController(request, handleCreateTopicsRequest) case ApiKeys.DELETE_TOPICS => maybeForwardToController(request, handleDeleteTopicsRequest) case ApiKeys.DELETE_RECORDS => handleDeleteRecordsRequest(request) - case ApiKeys.INIT_PRODUCER_ID => handleInitProducerIdRequest(request) + case ApiKeys.INIT_PRODUCER_ID => handleInitProducerIdRequest(request, requestLocal) case ApiKeys.OFFSET_FOR_LEADER_EPOCH => handleOffsetForLeaderEpochRequest(request) - case ApiKeys.ADD_PARTITIONS_TO_TXN => handleAddPartitionToTxnRequest(request) - case ApiKeys.ADD_OFFSETS_TO_TXN => handleAddOffsetsToTxnRequest(request) - case ApiKeys.END_TXN => handleEndTxnRequest(request) - case ApiKeys.WRITE_TXN_MARKERS => handleWriteTxnMarkersRequest(request) - case ApiKeys.TXN_OFFSET_COMMIT => handleTxnOffsetCommitRequest(request) + case ApiKeys.ADD_PARTITIONS_TO_TXN => handleAddPartitionToTxnRequest(request, requestLocal) + case ApiKeys.ADD_OFFSETS_TO_TXN => handleAddOffsetsToTxnRequest(request, requestLocal) + case ApiKeys.END_TXN => handleEndTxnRequest(request, requestLocal) + case ApiKeys.WRITE_TXN_MARKERS => handleWriteTxnMarkersRequest(request, requestLocal) + case ApiKeys.TXN_OFFSET_COMMIT => handleTxnOffsetCommitRequest(request, requestLocal) case ApiKeys.DESCRIBE_ACLS => handleDescribeAcls(request) case ApiKeys.CREATE_ACLS => maybeForwardToController(request, handleCreateAcls) case ApiKeys.DELETE_ACLS => maybeForwardToController(request, handleDeleteAcls) @@ -198,25 +198,25 @@ class KafkaApis(val requestChannel: RequestChannel, case ApiKeys.RENEW_DELEGATION_TOKEN => maybeForwardToController(request, handleRenewTokenRequest) case ApiKeys.EXPIRE_DELEGATION_TOKEN => maybeForwardToController(request, handleExpireTokenRequest) case ApiKeys.DESCRIBE_DELEGATION_TOKEN => handleDescribeTokensRequest(request) - case ApiKeys.DELETE_GROUPS => handleDeleteGroupsRequest(request) + case ApiKeys.DELETE_GROUPS => handleDeleteGroupsRequest(request, requestLocal) case ApiKeys.ELECT_LEADERS => handleElectReplicaLeader(request) case ApiKeys.INCREMENTAL_ALTER_CONFIGS => maybeForwardToController(request, handleIncrementalAlterConfigsRequest) case ApiKeys.ALTER_PARTITION_REASSIGNMENTS => maybeForwardToController(request, handleAlterPartitionReassignmentsRequest) case ApiKeys.LIST_PARTITION_REASSIGNMENTS => handleListPartitionReassignmentsRequest(request) - case ApiKeys.OFFSET_DELETE => handleOffsetDeleteRequest(request) + case ApiKeys.OFFSET_DELETE => handleOffsetDeleteRequest(request, requestLocal) case ApiKeys.DESCRIBE_CLIENT_QUOTAS => handleDescribeClientQuotasRequest(request) case ApiKeys.ALTER_CLIENT_QUOTAS => maybeForwardToController(request, handleAlterClientQuotasRequest) case ApiKeys.DESCRIBE_USER_SCRAM_CREDENTIALS => handleDescribeUserScramCredentialsRequest(request) case ApiKeys.ALTER_USER_SCRAM_CREDENTIALS => maybeForwardToController(request, handleAlterUserScramCredentialsRequest) case ApiKeys.ALTER_ISR => handleAlterIsrRequest(request) case ApiKeys.UPDATE_FEATURES => maybeForwardToController(request, handleUpdateFeatures) - case ApiKeys.ENVELOPE => handleEnvelope(request) + case ApiKeys.ENVELOPE => handleEnvelope(request, requestLocal) case ApiKeys.DESCRIBE_CLUSTER => handleDescribeCluster(request) case ApiKeys.DESCRIBE_PRODUCERS => handleDescribeProducersRequest(request) case ApiKeys.UNREGISTER_BROKER => maybeForwardToController(request, handleUnregisterBrokerRequest) case ApiKeys.DESCRIBE_TRANSACTIONS => handleDescribeTransactionsRequest(request) case ApiKeys.LIST_TRANSACTIONS => handleListTransactionsRequest(request) - case ApiKeys.ALLOCATE_PRODUCER_IDS => handleAllocateProducerIdsRequest(request) + case ApiKeys.ALLOCATE_PRODUCER_IDS => maybeForwardToController(request, handleAllocateProducerIdsRequest) case _ => throw new IllegalStateException(s"No handler for request api key ${request.header.apiKey}") } } catch { @@ -283,14 +283,18 @@ class KafkaApis(val requestChannel: RequestChannel, // cannot rely on the LeaderAndIsr API for this since it is only sent to active replicas. result.forKeyValue { (topicPartition, error) => if (error == Errors.NONE) { + val partitionState = partitionStates(topicPartition) if (topicPartition.topic == GROUP_METADATA_TOPIC_NAME - && partitionStates(topicPartition).deletePartition) { - groupCoordinator.onResignation(topicPartition.partition) + && partitionState.deletePartition) { + val leaderEpoch = if (partitionState.leaderEpoch >= 0) + Some(partitionState.leaderEpoch) + else + None + groupCoordinator.onResignation(topicPartition.partition, leaderEpoch) } else if (topicPartition.topic == TRANSACTION_STATE_TOPIC_NAME - && partitionStates(topicPartition).deletePartition) { - val partitionState = partitionStates(topicPartition) + && partitionState.deletePartition) { val leaderEpoch = if (partitionState.leaderEpoch >= 0) - Some(partitionState.leaderEpoch) + Some(partitionState.leaderEpoch) else None txnCoordinator.onResignation(topicPartition.partition, coordinatorEpoch = leaderEpoch) @@ -314,7 +318,7 @@ class KafkaApis(val requestChannel: RequestChannel, CoreUtils.swallow(replicaManager.replicaFetcherManager.shutdownIdleFetcherThreads(), this) } - def handleUpdateMetadataRequest(request: RequestChannel.Request): Unit = { + def handleUpdateMetadataRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { val zkSupport = metadataSupport.requireZkOrThrow(KafkaApis.shouldNeverReceive(request)) val correlationId = request.header.correlationId val updateMetadataRequest = request.body[UpdateMetadataRequest] @@ -330,13 +334,14 @@ class KafkaApis(val requestChannel: RequestChannel, } else { val deletedPartitions = replicaManager.maybeUpdateMetadataCache(correlationId, updateMetadataRequest) if (deletedPartitions.nonEmpty) - groupCoordinator.handleDeletedPartitions(deletedPartitions) + groupCoordinator.handleDeletedPartitions(deletedPartitions, requestLocal) if (zkSupport.adminManager.hasDelayedTopicOperations) { updateMetadataRequest.partitionStates.forEach { partitionState => zkSupport.adminManager.tryCompleteDelayedTopicOperations(partitionState.topicName) } } + quotas.clientQuotaCallback.foreach { callback => if (callback.updateClusterMetadata(metadataCache.getClusterMetadata(clusterId, request.context.listenerName))) { quotas.fetch.updateQuotaMetricConfigs() @@ -380,7 +385,7 @@ class KafkaApis(val requestChannel: RequestChannel, /** * Handle an offset commit request */ - def handleOffsetCommitRequest(request: RequestChannel.Request): Unit = { + def handleOffsetCommitRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { val header = request.header val offsetCommitRequest = request.body[OffsetCommitRequest] @@ -509,7 +514,8 @@ class KafkaApis(val requestChannel: RequestChannel, Option(offsetCommitRequest.data.groupInstanceId), offsetCommitRequest.data.generationId, partitionData, - sendResponseCallback) + sendResponseCallback, + requestLocal) } } } @@ -517,7 +523,7 @@ class KafkaApis(val requestChannel: RequestChannel, /** * Handle a produce request */ - def handleProduceRequest(request: RequestChannel.Request): Unit = { + def handleProduceRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { val produceRequest = request.body[ProduceRequest] val requestSize = request.sizeInBytes @@ -639,6 +645,7 @@ class KafkaApis(val requestChannel: RequestChannel, internalTopicsAllowed = internalTopicsAllowed, origin = AppendOrigin.Client, entriesPerPartition = authorizedRequestInfo, + requestLocal = requestLocal, responseCallback = sendResponseCallback, recordConversionStatsCallback = processingStatsCallback) @@ -1448,7 +1455,7 @@ class KafkaApis(val requestChannel: RequestChannel, } } - def handleJoinGroupRequest(request: RequestChannel.Request): Unit = { + def handleJoinGroupRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { val joinGroupRequest = request.body[JoinGroupRequest] // the callback for sending a join-group response @@ -1507,11 +1514,12 @@ class KafkaApis(val requestChannel: RequestChannel, joinGroupRequest.data.sessionTimeoutMs, joinGroupRequest.data.protocolType, protocols, - sendResponseCallback) + sendResponseCallback, + requestLocal) } } - def handleSyncGroupRequest(request: RequestChannel.Request): Unit = { + def handleSyncGroupRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { val syncGroupRequest = request.body[SyncGroupRequest] def sendResponseCallback(syncGroupResult: SyncGroupResult): Unit = { @@ -1550,19 +1558,20 @@ class KafkaApis(val requestChannel: RequestChannel, Option(syncGroupRequest.data.protocolName), Option(syncGroupRequest.data.groupInstanceId), assignmentMap.result(), - sendResponseCallback + sendResponseCallback, + requestLocal ) } } - def handleDeleteGroupsRequest(request: RequestChannel.Request): Unit = { + def handleDeleteGroupsRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { val deleteGroupsRequest = request.body[DeleteGroupsRequest] val groups = deleteGroupsRequest.data.groupsNames.asScala.distinct val (authorizedGroups, unauthorizedGroups) = authHelper.partitionSeqByAuthorized(request.context, DELETE, GROUP, groups)(identity) - val groupDeletionResult = groupCoordinator.handleDeleteGroups(authorizedGroups.toSet) ++ + val groupDeletionResult = groupCoordinator.handleDeleteGroups(authorizedGroups.toSet, requestLocal) ++ unauthorizedGroups.map(_ -> Errors.GROUP_AUTHORIZATION_FAILED) requestHelper.sendResponseMaybeThrottle(request, requestThrottleMs => { @@ -1990,7 +1999,7 @@ class KafkaApis(val requestChannel: RequestChannel, } } - def handleInitProducerIdRequest(request: RequestChannel.Request): Unit = { + def handleInitProducerIdRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { val initProducerIdRequest = request.body[InitProducerIdRequest] val transactionalId = initProducerIdRequest.data.transactionalId @@ -2035,12 +2044,12 @@ class KafkaApis(val requestChannel: RequestChannel, producerIdAndEpoch match { case Right(producerIdAndEpoch) => txnCoordinator.handleInitProducerId(transactionalId, initProducerIdRequest.data.transactionTimeoutMs, - producerIdAndEpoch, sendResponseCallback) + producerIdAndEpoch, sendResponseCallback, requestLocal) case Left(error) => requestHelper.sendErrorResponseMaybeThrottle(request, error.exception) } } - def handleEndTxnRequest(request: RequestChannel.Request): Unit = { + def handleEndTxnRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { ensureInterBrokerVersion(KAFKA_0_11_0_IV0) val endTxnRequest = request.body[EndTxnRequest] val transactionalId = endTxnRequest.data.transactionalId @@ -2071,7 +2080,8 @@ class KafkaApis(val requestChannel: RequestChannel, endTxnRequest.data.producerId, endTxnRequest.data.producerEpoch, endTxnRequest.result(), - sendResponseCallback) + sendResponseCallback, + requestLocal) } else requestHelper.sendResponseMaybeThrottle(request, requestThrottleMs => new EndTxnResponse(new EndTxnResponseData() @@ -2080,7 +2090,7 @@ class KafkaApis(val requestChannel: RequestChannel, ) } - def handleWriteTxnMarkersRequest(request: RequestChannel.Request): Unit = { + def handleWriteTxnMarkersRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { ensureInterBrokerVersion(KAFKA_0_11_0_IV0) authHelper.authorizeClusterOperation(request, CLUSTER_ACTION) val writeTxnMarkersRequest = request.body[WriteTxnMarkersRequest] @@ -2175,6 +2185,7 @@ class KafkaApis(val requestChannel: RequestChannel, internalTopicsAllowed = true, origin = AppendOrigin.Coordinator, entriesPerPartition = controlRecords, + requestLocal = requestLocal, responseCallback = maybeSendResponseCallback(producerId, marker.transactionResult)) } } @@ -2190,7 +2201,7 @@ class KafkaApis(val requestChannel: RequestChannel, throw new UnsupportedVersionException(s"inter.broker.protocol.version: ${config.interBrokerProtocolVersion.version} is less than the required version: ${version.version}") } - def handleAddPartitionToTxnRequest(request: RequestChannel.Request): Unit = { + def handleAddPartitionToTxnRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { ensureInterBrokerVersion(KAFKA_0_11_0_IV0) val addPartitionsToTxnRequest = request.body[AddPartitionsToTxnRequest] val transactionalId = addPartitionsToTxnRequest.data.transactionalId @@ -2240,7 +2251,6 @@ class KafkaApis(val requestChannel: RequestChannel, responseBody } - requestHelper.sendResponseMaybeThrottle(request, createResponse) } @@ -2248,12 +2258,13 @@ class KafkaApis(val requestChannel: RequestChannel, addPartitionsToTxnRequest.data.producerId, addPartitionsToTxnRequest.data.producerEpoch, authorizedPartitions, - sendResponseCallback) + sendResponseCallback, + requestLocal) } } } - def handleAddOffsetsToTxnRequest(request: RequestChannel.Request): Unit = { + def handleAddOffsetsToTxnRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { ensureInterBrokerVersion(KAFKA_0_11_0_IV0) val addOffsetsToTxnRequest = request.body[AddOffsetsToTxnRequest] val transactionalId = addOffsetsToTxnRequest.data.transactionalId @@ -2298,11 +2309,12 @@ class KafkaApis(val requestChannel: RequestChannel, addOffsetsToTxnRequest.data.producerId, addOffsetsToTxnRequest.data.producerEpoch, Set(offsetTopicPartition), - sendResponseCallback) + sendResponseCallback, + requestLocal) } } - def handleTxnOffsetCommitRequest(request: RequestChannel.Request): Unit = { + def handleTxnOffsetCommitRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { ensureInterBrokerVersion(KAFKA_0_11_0_IV0) val header = request.header val txnOffsetCommitRequest = request.body[TxnOffsetCommitRequest] @@ -2367,7 +2379,8 @@ class KafkaApis(val requestChannel: RequestChannel, Option(txnOffsetCommitRequest.data.groupInstanceId), txnOffsetCommitRequest.data.generationId, offsetMetadata, - sendResponseCallback) + sendResponseCallback, + requestLocal) } } } @@ -2877,7 +2890,7 @@ class KafkaApis(val requestChannel: RequestChannel, } } - def handleOffsetDeleteRequest(request: RequestChannel.Request): Unit = { + def handleOffsetDeleteRequest(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { val offsetDeleteRequest = request.body[OffsetDeleteRequest] val groupId = offsetDeleteRequest.data.groupId @@ -2901,7 +2914,7 @@ class KafkaApis(val requestChannel: RequestChannel, } val (groupError, authorizedTopicPartitionsErrors) = groupCoordinator.handleDeleteOffsets( - groupId, topicPartitions) + groupId, topicPartitions, requestLocal) topicPartitionErrors ++= authorizedTopicPartitionsErrors @@ -3129,7 +3142,7 @@ class KafkaApis(val requestChannel: RequestChannel, }) } - def handleEnvelope(request: RequestChannel.Request): Unit = { + def handleEnvelope(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { val zkSupport = metadataSupport.requireZkOrThrow(KafkaApis.shouldNeverReceive(request)) // If forwarding is not yet enabled or this request has been received on an invalid endpoint, @@ -3154,7 +3167,8 @@ class KafkaApis(val requestChannel: RequestChannel, s"Broker $brokerId is not the active controller")) return } - EnvelopeUtils.handleEnvelopeRequest(request, requestChannel.metrics, handle) + + EnvelopeUtils.handleEnvelopeRequest(request, requestChannel.metrics, handle(_, requestLocal)) } def handleDescribeProducersRequest(request: RequestChannel.Request): Unit = { diff --git a/core/src/main/scala/kafka/server/KafkaRequestHandler.scala b/core/src/main/scala/kafka/server/KafkaRequestHandler.scala index f6d868ade7546..4d38c6efc3fd6 100755 --- a/core/src/main/scala/kafka/server/KafkaRequestHandler.scala +++ b/core/src/main/scala/kafka/server/KafkaRequestHandler.scala @@ -20,9 +20,9 @@ package kafka.server import kafka.network._ import kafka.utils._ import kafka.metrics.KafkaMetricsGroup + import java.util.concurrent.{CountDownLatch, TimeUnit} import java.util.concurrent.atomic.AtomicInteger - import com.yammer.metrics.core.Meter import org.apache.kafka.common.internals.FatalExitError import org.apache.kafka.common.utils.{KafkaThread, Time} @@ -31,7 +31,7 @@ import scala.collection.mutable import scala.jdk.CollectionConverters._ trait ApiRequestHandler { - def handle(request: RequestChannel.Request): Unit + def handle(request: RequestChannel.Request, requestLocal: RequestLocal): Unit } /** @@ -44,8 +44,9 @@ class KafkaRequestHandler(id: Int, val requestChannel: RequestChannel, apis: ApiRequestHandler, time: Time) extends Runnable with Logging { - this.logIdent = "[Kafka Request Handler " + id + " on Broker " + brokerId + "], " + this.logIdent = s"[Kafka Request Handler $id on Broker $brokerId], " private val shutdownComplete = new CountDownLatch(1) + private val requestLocal = RequestLocal.withThreadConfinedCaching @volatile private var stopped = false def run(): Unit = { @@ -64,17 +65,17 @@ class KafkaRequestHandler(id: Int, req match { case RequestChannel.ShutdownRequest => debug(s"Kafka request handler $id on broker $brokerId received shut down command") - shutdownComplete.countDown() + completeShutdown() return case request: RequestChannel.Request => try { request.requestDequeueTimeNanos = endTime trace(s"Kafka request handler $id on broker $brokerId handling request $request") - apis.handle(request) + apis.handle(request, requestLocal) } catch { case e: FatalExitError => - shutdownComplete.countDown() + completeShutdown() Exit.exit(e.statusCode) case e: Throwable => error("Exception when handling request", e) } finally { @@ -84,6 +85,11 @@ class KafkaRequestHandler(id: Int, case null => // continue } } + completeShutdown() + } + + private def completeShutdown(): Unit = { + requestLocal.close() shutdownComplete.countDown() } diff --git a/core/src/main/scala/kafka/server/PartitionMetadataFile.scala b/core/src/main/scala/kafka/server/PartitionMetadataFile.scala index 25b1ba6129d2b..c08c87dabe492 100644 --- a/core/src/main/scala/kafka/server/PartitionMetadataFile.scala +++ b/core/src/main/scala/kafka/server/PartitionMetadataFile.scala @@ -138,7 +138,9 @@ class PartitionMetadataFile(val file: File, file.exists() } - def delete(): Boolean = { - file.delete() + def delete(): Unit = { + Files.delete(file.toPath) } + + override def toString: String = s"PartitionMetadataFile(path=$path)" } diff --git a/core/src/main/scala/kafka/server/ReplicaManager.scala b/core/src/main/scala/kafka/server/ReplicaManager.scala index d813241bf3058..6ca8169bf3abe 100644 --- a/core/src/main/scala/kafka/server/ReplicaManager.scala +++ b/core/src/main/scala/kafka/server/ReplicaManager.scala @@ -21,7 +21,6 @@ import java.util.Optional import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.locks.Lock - import com.yammer.metrics.core.Meter import kafka.api._ import kafka.cluster.{BrokerEndPoint, Partition} @@ -606,11 +605,12 @@ class ReplicaManager(val config: KafkaConfig, entriesPerPartition: Map[TopicPartition, MemoryRecords], responseCallback: Map[TopicPartition, PartitionResponse] => Unit, delayedProduceLock: Option[Lock] = None, - recordConversionStatsCallback: Map[TopicPartition, RecordConversionStats] => Unit = _ => ()): Unit = { + recordConversionStatsCallback: Map[TopicPartition, RecordConversionStats] => Unit = _ => (), + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { if (isValidRequiredAcks(requiredAcks)) { val sTime = time.milliseconds val localProduceResults = appendToLocalLog(internalTopicsAllowed = internalTopicsAllowed, - origin, entriesPerPartition, requiredAcks) + origin, entriesPerPartition, requiredAcks, requestLocal) debug("Produce to local log in %d ms".format(time.milliseconds - sTime)) val produceStatus = localProduceResults.map { case (topicPartition, result) => @@ -926,7 +926,8 @@ class ReplicaManager(val config: KafkaConfig, private def appendToLocalLog(internalTopicsAllowed: Boolean, origin: AppendOrigin, entriesPerPartition: Map[TopicPartition, MemoryRecords], - requiredAcks: Short): Map[TopicPartition, LogAppendResult] = { + requiredAcks: Short, + requestLocal: RequestLocal): Map[TopicPartition, LogAppendResult] = { val traceEnabled = isTraceEnabled def processFailedRecord(topicPartition: TopicPartition, t: Throwable) = { val logStartOffset = onlinePartition(topicPartition).map(_.logStartOffset).getOrElse(-1L) @@ -952,7 +953,7 @@ class ReplicaManager(val config: KafkaConfig, } else { try { val partition = getPartitionOrException(topicPartition) - val info = partition.appendRecordsToLeader(records, origin, requiredAcks) + val info = partition.appendRecordsToLeader(records, origin, requiredAcks, requestLocal) val numAppendedMessages = info.numMessages // update stats for successfully appended bytes and messages as bytesInRate and messageInRate diff --git a/core/src/main/scala/kafka/server/RequestHandlerHelper.scala b/core/src/main/scala/kafka/server/RequestHandlerHelper.scala index ef9ff8210b53d..a1aab617b3a28 100644 --- a/core/src/main/scala/kafka/server/RequestHandlerHelper.scala +++ b/core/src/main/scala/kafka/server/RequestHandlerHelper.scala @@ -39,14 +39,14 @@ object RequestHandlerHelper { // leadership changes updatedLeaders.foreach { partition => if (partition.topic == Topic.GROUP_METADATA_TOPIC_NAME) - groupCoordinator.onElection(partition.partitionId) + groupCoordinator.onElection(partition.partitionId, partition.getLeaderEpoch) else if (partition.topic == Topic.TRANSACTION_STATE_TOPIC_NAME) txnCoordinator.onElection(partition.partitionId, partition.getLeaderEpoch) } updatedFollowers.foreach { partition => if (partition.topic == Topic.GROUP_METADATA_TOPIC_NAME) - groupCoordinator.onResignation(partition.partitionId) + groupCoordinator.onResignation(partition.partitionId, Some(partition.getLeaderEpoch)) else if (partition.topic == Topic.TRANSACTION_STATE_TOPIC_NAME) txnCoordinator.onResignation(partition.partitionId, Some(partition.getLeaderEpoch)) } diff --git a/core/src/main/scala/kafka/server/RequestLocal.scala b/core/src/main/scala/kafka/server/RequestLocal.scala new file mode 100644 index 0000000000000..5af495f8675f8 --- /dev/null +++ b/core/src/main/scala/kafka/server/RequestLocal.scala @@ -0,0 +1,37 @@ +/** + * 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 kafka.server + +import org.apache.kafka.common.utils.BufferSupplier + +object RequestLocal { + val NoCaching: RequestLocal = RequestLocal(BufferSupplier.NO_CACHING) + + /** The returned instance should be confined to a single thread. */ + def withThreadConfinedCaching: RequestLocal = RequestLocal(BufferSupplier.create()) +} + +/** + * Container for stateful instances where the lifecycle is scoped to one request. + * + * When each request is handled by one thread, efficient data structures with no locking or atomic operations + * can be used (see RequestLocal.withThreadConfinedCaching). + */ +case class RequestLocal(bufferSupplier: BufferSupplier) { + def close(): Unit = bufferSupplier.close() +} diff --git a/core/src/main/scala/kafka/server/metadata/BrokerMetadataListener.scala b/core/src/main/scala/kafka/server/metadata/BrokerMetadataListener.scala index 6dfaa1800406c..4b4d15ef48e6c 100644 --- a/core/src/main/scala/kafka/server/metadata/BrokerMetadataListener.scala +++ b/core/src/main/scala/kafka/server/metadata/BrokerMetadataListener.scala @@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit import kafka.coordinator.group.GroupCoordinator import kafka.coordinator.transaction.TransactionCoordinator import kafka.metrics.KafkaMetricsGroup -import kafka.server.{RaftReplicaManager, RequestHandlerHelper} +import kafka.server.{RaftReplicaManager, RequestHandlerHelper, RequestLocal} import org.apache.kafka.common.config.ConfigResource import org.apache.kafka.common.metadata.MetadataRecordType._ import org.apache.kafka.common.metadata._ @@ -182,6 +182,7 @@ class BrokerMetadataListener( case rec: RemoveTopicRecord => handleRemoveTopicRecord(imageBuilder, rec) case rec: ConfigRecord => handleConfigRecord(rec) case rec: QuotaRecord => handleQuotaRecord(imageBuilder, rec) + case rec: ProducerIdsRecord => handleProducerIdRecord(rec) case _ => throw new RuntimeException(s"Unhandled record $record with type $recordType") } } @@ -248,7 +249,7 @@ class BrokerMetadataListener( case Some(topicName) => info(s"Processing deletion of topic $topicName with id ${record.topicId}") val removedPartitions = imageBuilder.partitionsBuilder().removeTopicById(record.topicId()) - groupCoordinator.handleDeletedPartitions(removedPartitions.map(_.toTopicPartition).toSeq) + groupCoordinator.handleDeletedPartitions(removedPartitions.map(_.toTopicPartition).toSeq, RequestLocal.NoCaching) configRepository.remove(new ConfigResource(ConfigResource.Type.TOPIC, topicName)) } } @@ -259,6 +260,11 @@ class BrokerMetadataListener( clientQuotaManager.handleQuotaRecord(record) } + def handleProducerIdRecord(record: ProducerIdsRecord): Unit = { + // This is a no-op since brokers get their producer ID blocks directly from the controller via + // AllocateProducerIds RPC response + } + class HandleNewLeaderEvent(leaderAndEpoch: LeaderAndEpoch) extends EventQueue.FailureLoggingEvent(log) { override def run(): Unit = { diff --git a/core/src/main/scala/kafka/tools/TestRaftRequestHandler.scala b/core/src/main/scala/kafka/tools/TestRaftRequestHandler.scala index 91d5ecd3997ac..a9b471b1622ba 100644 --- a/core/src/main/scala/kafka/tools/TestRaftRequestHandler.scala +++ b/core/src/main/scala/kafka/tools/TestRaftRequestHandler.scala @@ -19,7 +19,7 @@ package kafka.tools import kafka.network.RequestChannel import kafka.raft.RaftManager -import kafka.server.{ApiRequestHandler, ApiVersionManager} +import kafka.server.{ApiRequestHandler, ApiVersionManager, RequestLocal} import kafka.utils.Logging import org.apache.kafka.common.internals.FatalExitError import org.apache.kafka.common.message.{BeginQuorumEpochResponseData, EndQuorumEpochResponseData, FetchResponseData, FetchSnapshotResponseData, VoteResponseData} @@ -37,7 +37,7 @@ class TestRaftRequestHandler( apiVersionManager: ApiVersionManager ) extends ApiRequestHandler with Logging { - override def handle(request: RequestChannel.Request): Unit = { + override def handle(request: RequestChannel.Request, requestLocal: RequestLocal): Unit = { try { trace(s"Handling request:${request.requestDesc(true)} with context ${request.context}") request.header.apiKey match { diff --git a/core/src/main/scala/kafka/zk/KafkaZkClient.scala b/core/src/main/scala/kafka/zk/KafkaZkClient.scala index 4b7bc466280b1..bcc89de9b7f77 100644 --- a/core/src/main/scala/kafka/zk/KafkaZkClient.scala +++ b/core/src/main/scala/kafka/zk/KafkaZkClient.scala @@ -1954,7 +1954,9 @@ object KafkaZkClient { connectionTimeoutMs, maxInFlightRequests, time, metricGroup, metricType, name, zkClientConfig) try { val chroot = connectString.substring(chrootIndex) - zkClientForChrootCreation.makeSurePersistentPathExists(chroot) + if (!zkClientForChrootCreation.pathExists(chroot)) { + zkClientForChrootCreation.makeSurePersistentPathExists(chroot) + } } finally { zkClientForChrootCreation.close() } diff --git a/core/src/test/java/kafka/test/MockController.java b/core/src/test/java/kafka/test/MockController.java index 1fba2952165b0..68e73fd331bd7 100644 --- a/core/src/test/java/kafka/test/MockController.java +++ b/core/src/test/java/kafka/test/MockController.java @@ -21,6 +21,8 @@ import org.apache.kafka.common.Uuid; import org.apache.kafka.common.config.ConfigResource; import org.apache.kafka.common.errors.NotControllerException; +import org.apache.kafka.common.message.AllocateProducerIdsRequestData; +import org.apache.kafka.common.message.AllocateProducerIdsResponseData; import org.apache.kafka.common.message.AlterIsrRequestData; import org.apache.kafka.common.message.AlterIsrResponseData; import org.apache.kafka.common.message.AlterPartitionReassignmentsRequestData; @@ -301,6 +303,11 @@ public CompletableFuture waitForReadyBrokers(int minBrokers) { throw new UnsupportedOperationException(); } + @Override + public CompletableFuture allocateProducerIds(AllocateProducerIdsRequestData request) { + throw new UnsupportedOperationException(); + } + @Override synchronized public CompletableFuture> createPartitions(long deadlineNs, List topicList) { diff --git a/core/src/test/scala/integration/kafka/admin/ReassignPartitionsIntegrationTest.scala b/core/src/test/scala/integration/kafka/admin/ReassignPartitionsIntegrationTest.scala index 07c5c2ad0bd1d..490d177a94119 100644 --- a/core/src/test/scala/integration/kafka/admin/ReassignPartitionsIntegrationTest.scala +++ b/core/src/test/scala/integration/kafka/admin/ReassignPartitionsIntegrationTest.scala @@ -24,7 +24,7 @@ import kafka.api.KAFKA_2_7_IV1 import kafka.server.{IsrChangePropagationConfig, KafkaConfig, KafkaServer, ZkIsrManager} import kafka.utils.Implicits._ import kafka.utils.TestUtils -import kafka.zk.{KafkaZkClient, ZooKeeperTestHarness} +import kafka.zk.ZooKeeperTestHarness import org.apache.kafka.clients.admin.{Admin, AdminClientConfig, AlterConfigOp, ConfigEntry, DescribeLogDirsResult, NewTopic} import org.apache.kafka.clients.producer.ProducerRecord import org.apache.kafka.common.config.ConfigResource @@ -115,8 +115,6 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { ) waitForVerifyAssignment(cluster.adminClient, assignment, false, VerifyAssignmentResult(initialAssignment)) - waitForVerifyAssignment(zkClient, assignment, false, - VerifyAssignmentResult(initialAssignment)) // Execute the assignment runExecuteAssignment(cluster.adminClient, false, assignment, -1L, -1L) @@ -128,43 +126,18 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { PartitionReassignmentState(Seq(3, 2, 0), Seq(3, 2, 0), true) ) - // When using --zookeeper, we aren't able to see the new-style assignment - assertFalse(runVerifyAssignment(zkClient, assignment, false).movesOngoing) + val verifyAssignmentResult = runVerifyAssignment(cluster.adminClient, assignment, false) + assertTrue(verifyAssignmentResult.partsOngoing) + assertFalse(verifyAssignmentResult.movesOngoing) // Wait for the assignment to complete - waitForVerifyAssignment(zkClient, assignment, false, + waitForVerifyAssignment(cluster.adminClient, assignment, false, VerifyAssignmentResult(finalAssignment)) assertEquals(unthrottledBrokerConfigs, describeBrokerLevelThrottles(unthrottledBrokerConfigs.keySet.toSeq)) } - /** - * Test running a quick reassignment with the --zookeeper option. - */ - @Test - def testLegacyReassignment(): Unit = { - cluster = new ReassignPartitionsTestCluster(zkConnect) - cluster.setup() - val assignment = """{"version":1,"partitions":""" + - """[{"topic":"foo","partition":0,"replicas":[0,1,3],"log_dirs":["any","any","any"]},""" + - """{"topic":"bar","partition":0,"replicas":[3,2,0],"log_dirs":["any","any","any"]}""" + - """]}""" - // Execute the assignment - runExecuteAssignment(zkClient, assignment, -1L) - val finalAssignment = Map( - new TopicPartition("foo", 0) -> - PartitionReassignmentState(Seq(0, 1, 3), Seq(0, 1, 3), true), - new TopicPartition("bar", 0) -> - PartitionReassignmentState(Seq(3, 2, 0), Seq(3, 2, 0), true) - ) - // Wait for the assignment to complete - waitForVerifyAssignment(cluster.adminClient, assignment, false, - VerifyAssignmentResult(finalAssignment)) - waitForVerifyAssignment(zkClient, assignment, false, - VerifyAssignmentResult(finalAssignment)) - } - @Test def testHighWaterMarkAfterPartitionReassignment(): Unit = { cluster = new ReassignPartitionsTestCluster(zkConnect) @@ -180,7 +153,7 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { // Execute the assignment runExecuteAssignment(cluster.adminClient, false, assignment, -1L, -1L) val finalAssignment = Map(part -> - PartitionReassignmentState(Seq(3, 1, 2), Seq(3, 1, 2), true)) + PartitionReassignmentState(Seq(3, 1, 2), Seq(3, 1, 2), true)) // Wait for the assignment to complete waitForVerifyAssignment(cluster.adminClient, assignment, false, @@ -189,7 +162,7 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { TestUtils.waitUntilTrue(() => { cluster.servers(3).replicaManager.onlinePartition(part). flatMap(_.leaderLogIfLocal).isDefined - }, "broker 3 should be the new leader", pause = 10L) + }, "broker 3 should be the new leader", pause = 10L) assertEquals(123L, cluster.servers(3).replicaManager.localLogOrException(part).highWatermark, s"Expected broker 3 to have the correct high water mark for the partition.") } @@ -248,7 +221,6 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { new TopicPartition("baz", 2) -> PartitionReassignmentState(Seq(0, 2, 1), Seq(3, 2, 1), true)) assertEquals(VerifyAssignmentResult(initialAssignment), runVerifyAssignment(cluster.adminClient, assignment, false)) - assertEquals(VerifyAssignmentResult(initialAssignment), runVerifyAssignment(zkClient, assignment, false)) assertEquals(unthrottledBrokerConfigs, describeBrokerLevelThrottles(unthrottledBrokerConfigs.keySet.toSeq)) // Execute the assignment @@ -280,8 +252,6 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { }, "Expected reassignment to complete.") waitForVerifyAssignment(cluster.adminClient, assignment, true, VerifyAssignmentResult(finalAssignment)) - waitForVerifyAssignment(zkClient, assignment, true, - VerifyAssignmentResult(finalAssignment)) // The throttles should still have been preserved, since we ran with --preserve-throttles waitForInterBrokerThrottle(Set(0, 1, 2, 3), interBrokerThrottle) // Now remove the throttles. @@ -340,12 +310,12 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { VerifyAssignmentResult(Map( new TopicPartition("foo", 0) -> PartitionReassignmentState(Seq(0, 1, 3, 2), Seq(0, 1, 3), false), new TopicPartition("baz", 1) -> PartitionReassignmentState(Seq(0, 2, 3, 1), Seq(0, 2, 3), false)), - true, Map(), false)) + true, Map(), false)) // Cancel the reassignment. assertEquals((Set( - new TopicPartition("foo", 0), - new TopicPartition("baz", 1) - ), Set()), runCancelAssignment(cluster.adminClient, assignment, true)) + new TopicPartition("foo", 0), + new TopicPartition("baz", 1) + ), Set()), runCancelAssignment(cluster.adminClient, assignment, true)) // Broker throttles are still active because we passed --preserve-throttles waitForInterBrokerThrottle(Set(0, 1, 2, 3), interBrokerThrottle) // Cancelling the reassignment again should reveal nothing to cancel. @@ -433,32 +403,32 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { // Check the output of --verify waitForVerifyAssignment(cluster.adminClient, reassignment.json, true, VerifyAssignmentResult(Map( - topicPartition -> PartitionReassignmentState(Seq(0, 1, 2), Seq(0, 1, 2), true) - ), false, Map( - new TopicPartitionReplica(topicPartition.topic, topicPartition.partition, 0) -> - ActiveMoveState(reassignment.currentDir, reassignment.targetDir, reassignment.targetDir) - ), true)) + topicPartition -> PartitionReassignmentState(Seq(0, 1, 2), Seq(0, 1, 2), true) + ), false, Map( + new TopicPartitionReplica(topicPartition.topic, topicPartition.partition, 0) -> + ActiveMoveState(reassignment.currentDir, reassignment.targetDir, reassignment.targetDir) + ), true)) waitForLogDirThrottle(Set(0), logDirThrottle) // Remove the throttle cluster.adminClient.incrementalAlterConfigs(Collections.singletonMap( new ConfigResource(ConfigResource.Type.BROKER, "0"), Collections.singletonList(new AlterConfigOp( - new ConfigEntry(brokerLevelLogDirThrottle, ""), AlterConfigOp.OpType.DELETE)))). - all().get() + new ConfigEntry(brokerLevelLogDirThrottle, ""), AlterConfigOp.OpType.DELETE)))) + .all().get() waitForBrokerLevelThrottles(unthrottledBrokerConfigs) // Wait for the directory movement to complete. waitForVerifyAssignment(cluster.adminClient, reassignment.json, true, - VerifyAssignmentResult(Map( - topicPartition -> PartitionReassignmentState(Seq(0, 1, 2), Seq(0, 1, 2), true) - ), false, Map( - new TopicPartitionReplica(topicPartition.topic, topicPartition.partition, 0) -> - CompletedMoveState(reassignment.targetDir) - ), false)) + VerifyAssignmentResult(Map( + topicPartition -> PartitionReassignmentState(Seq(0, 1, 2), Seq(0, 1, 2), true) + ), false, Map( + new TopicPartitionReplica(topicPartition.topic, topicPartition.partition, 0) -> + CompletedMoveState(reassignment.targetDir) + ), false)) val info1 = new BrokerDirs(cluster.adminClient.describeLogDirs(0.to(4). - map(_.asInstanceOf[Integer]).asJavaCollection), 0) + map(_.asInstanceOf[Integer]).asJavaCollection), 0) assertEquals(reassignment.targetDir, info1.curLogDirs.getOrElse(topicPartition, "")) } @@ -540,7 +510,8 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { verifyAssignment(adminClient, jsonString, preserveThrottles) } - private def waitForVerifyAssignment(adminClient: Admin, jsonString: String, + private def waitForVerifyAssignment(adminClient: Admin, + jsonString: String, preserveThrottles: Boolean, expectedResult: VerifyAssignmentResult): Unit = { var latestResult: VerifyAssignmentResult = null @@ -552,26 +523,6 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { s"The latest result was ${latestResult}", pause = 10L) } - private def runVerifyAssignment(zkClient: KafkaZkClient, jsonString: String, - preserveThrottles: Boolean) = { - println(s"==> verifyAssignment(zkClient, jsonString=${jsonString})") - verifyAssignment(zkClient, jsonString, preserveThrottles) - } - - private def waitForVerifyAssignment(zkClient: KafkaZkClient, jsonString: String, - preserveThrottles: Boolean, - expectedResult: VerifyAssignmentResult): Unit = { - var latestResult: VerifyAssignmentResult = null - TestUtils.waitUntilTrue( - () => { - println(s"==> verifyAssignment(zkClient, jsonString=${jsonString}, " + - s"preserveThrottles=${preserveThrottles})") - latestResult = verifyAssignment(zkClient, jsonString, preserveThrottles) - expectedResult.equals(latestResult) - }, s"Timed out waiting for verifyAssignment result ${expectedResult}. " + - s"The latest result was ${latestResult}", pause = 10L) - } - private def runExecuteAssignment(adminClient: Admin, additional: Boolean, reassignmentJson: String, @@ -585,15 +536,6 @@ class ReassignPartitionsIntegrationTest extends ZooKeeperTestHarness { interBrokerThrottle, replicaAlterLogDirsThrottle) } - private def runExecuteAssignment(zkClient: KafkaZkClient, - reassignmentJson: String, - interBrokerThrottle: Long) = { - println(s"==> executeAssignment(adminClient, " + - s"reassignmentJson=${reassignmentJson}, " + - s"interBrokerThrottle=${interBrokerThrottle})") - executeAssignment(zkClient, reassignmentJson, interBrokerThrottle) - } - private def runCancelAssignment(adminClient: Admin, jsonString: String, preserveThrottles: Boolean) = { println(s"==> cancelAssignment(adminClient, jsonString=${jsonString})") diff --git a/core/src/test/scala/integration/kafka/coordinator/transaction/ProducerIdsIntegrationTest.scala b/core/src/test/scala/integration/kafka/coordinator/transaction/ProducerIdsIntegrationTest.scala index 6c7c248e63188..d5e082a0a8e57 100644 --- a/core/src/test/scala/integration/kafka/coordinator/transaction/ProducerIdsIntegrationTest.scala +++ b/core/src/test/scala/integration/kafka/coordinator/transaction/ProducerIdsIntegrationTest.scala @@ -44,7 +44,8 @@ class ProducerIdsIntegrationTest { @ClusterTests(Array( new ClusterTest(clusterType = Type.ZK, brokers = 3, ibp = "2.8"), - new ClusterTest(clusterType = Type.ZK, brokers = 3, ibp = "3.0-IV0") + new ClusterTest(clusterType = Type.ZK, brokers = 3, ibp = "3.0-IV0"), + new ClusterTest(clusterType = Type.RAFT, brokers = 3, ibp = "3.0-IV0") )) def testUniqueProducerIds(clusterInstance: ClusterInstance): Unit = { verifyUniqueIds(clusterInstance) diff --git a/core/src/test/scala/unit/kafka/admin/ReassignPartitionsCommandArgsTest.scala b/core/src/test/scala/unit/kafka/admin/ReassignPartitionsCommandArgsTest.scala index d0c13ac143759..13fc262ec4e72 100644 --- a/core/src/test/scala/unit/kafka/admin/ReassignPartitionsCommandArgsTest.scala +++ b/core/src/test/scala/unit/kafka/admin/ReassignPartitionsCommandArgsTest.scala @@ -23,6 +23,8 @@ import org.junit.jupiter.api.{AfterEach, BeforeEach, Test, Timeout} @Timeout(60) class ReassignPartitionsCommandArgsTest { + val missingBootstrapServerMsg = "Please specify --bootstrap-server" + @BeforeEach def setUp(): Unit = { Exit.setExitProcedure((_, message) => throw new IllegalArgumentException(message.orNull)) @@ -53,15 +55,6 @@ class ReassignPartitionsCommandArgsTest { ReassignPartitionsCommand.validateAndParseArgs(args) } - @Test - def shouldCorrectlyParseValidMinimumLegacyExecuteOptions(): Unit = { - val args = Array( - "--zookeeper", "localhost:1234", - "--execute", - "--reassignment-json-file", "myfile.json") - ReassignPartitionsCommand.validateAndParseArgs(args) - } - @Test def shouldCorrectlyParseValidMinimumVerifyOptions(): Unit = { val args = Array( @@ -71,15 +64,6 @@ class ReassignPartitionsCommandArgsTest { ReassignPartitionsCommand.validateAndParseArgs(args) } - @Test - def shouldCorrectlyParseValidMinimumLegacyVerifyOptions(): Unit = { - val args = Array( - "--zookeeper", "localhost:1234", - "--verify", - "--reassignment-json-file", "myfile.json") - ReassignPartitionsCommand.validateAndParseArgs(args) - } - @Test def shouldAllowThrottleOptionOnExecute(): Unit = { val args = Array( @@ -177,7 +161,7 @@ class ReassignPartitionsCommandArgsTest { def testMissingBootstrapServerArgumentForExecute(): Unit = { val args = Array( "--execute") - shouldFailWith("Please specify --bootstrap-server", args) + shouldFailWith(missingBootstrapServerMsg, args) } ///// Test --generate @@ -230,15 +214,10 @@ class ReassignPartitionsCommandArgsTest { } @Test - def testInvalidCommandConfigArgumentForLegacyGenerate(): Unit = { - val args = Array( - "--zookeeper", "localhost:1234", - "--generate", - "--broker-list", "101,102", - "--topics-to-move-json-file", "myfile.json", - "--command-config", "/tmp/command-config.properties" - ) - shouldFailWith("You must specify --bootstrap-server when using \"[command-config]\"", args) + def shouldPrintHelpTextIfHelpArg(): Unit = { + val args: Array[String]= Array("--help") + // note, this is not actually a failed case, it's just we share the same `printUsageAndDie` method when wrong arg received + shouldFailWith(ReassignPartitionsCommand.helpText, args) } ///// Test --verify @@ -291,7 +270,7 @@ class ReassignPartitionsCommandArgsTest { def shouldNotAllowCancelWithoutBootstrapServerOption(): Unit = { val args = Array( "--cancel") - shouldFailWith("Please specify --bootstrap-server", args) + shouldFailWith(missingBootstrapServerMsg, args) } @Test @@ -302,13 +281,4 @@ class ReassignPartitionsCommandArgsTest { "--preserve-throttles") shouldFailWith("Missing required argument \"[reassignment-json-file]\"", args) } - - ///// Test --list - @Test - def shouldNotAllowZooKeeperWithListOption(): Unit = { - val args = Array( - "--list", - "--zookeeper", "localhost:1234") - shouldFailWith("Option \"[zookeeper]\" can't be used with action \"[list]\"", args) - } } diff --git a/core/src/test/scala/unit/kafka/admin/TopicCommandWithAdminClientTest.scala b/core/src/test/scala/unit/kafka/admin/TopicCommandIntegrationTest.scala similarity index 96% rename from core/src/test/scala/unit/kafka/admin/TopicCommandWithAdminClientTest.scala rename to core/src/test/scala/unit/kafka/admin/TopicCommandIntegrationTest.scala index 21f2bd30e5082..3c7a7b3b91887 100644 --- a/core/src/test/scala/unit/kafka/admin/TopicCommandWithAdminClientTest.scala +++ b/core/src/test/scala/unit/kafka/admin/TopicCommandIntegrationTest.scala @@ -16,33 +16,34 @@ */ package kafka.admin -import kafka.admin.TopicCommand.{AdminClientTopicService, TopicCommandOptions} +import java.util.{Collections, Optional, Properties} + +import kafka.admin.TopicCommand.{TopicCommandOptions, TopicService} import kafka.integration.KafkaServerTestHarness import kafka.server.{ConfigType, KafkaConfig} import kafka.utils.{Logging, TestUtils} import kafka.zk.{ConfigEntityChangeNotificationZNode, DeleteTopicsTopicZNode} import org.apache.kafka.clients.CommonClientConfigs import org.apache.kafka.clients.admin._ -import org.apache.kafka.common.{Node, TopicPartition, TopicPartitionInfo} import org.apache.kafka.common.config.{ConfigException, ConfigResource, TopicConfig} import org.apache.kafka.common.errors.{ClusterAuthorizationException, ThrottlingQuotaExceededException, TopicExistsException} import org.apache.kafka.common.internals.Topic import org.apache.kafka.common.network.ListenerName import org.apache.kafka.common.protocol.Errors import org.apache.kafka.common.security.auth.SecurityProtocol +import org.apache.kafka.common.{Node, TopicPartition, TopicPartitionInfo} import org.junit.jupiter.api.Assertions._ import org.junit.jupiter.api.{AfterEach, BeforeEach, Test, TestInfo} import org.mockito.ArgumentMatcher import org.mockito.ArgumentMatchers.{eq => eqThat, _} import org.mockito.Mockito._ -import java.util.{Collections, Optional, Properties} import scala.collection.Seq import scala.concurrent.ExecutionException import scala.jdk.CollectionConverters._ import scala.util.Random -class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Logging with RackAwareTest { +class TopicCommandIntegrationTest extends KafkaServerTestHarness with Logging with RackAwareTest { /** * Implementations must override this method to return a set of KafkaConfigs. This method will be invoked for every @@ -65,7 +66,7 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin private val numPartitions = 1 private val defaultReplicationFactor = 1.toShort - private var topicService: AdminClientTopicService = _ + private var topicService: TopicService = _ private var adminClient: Admin = _ private var testTopicName: String = _ @@ -84,7 +85,7 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin val props = new Properties() props.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, brokerList) adminClient = Admin.create(props) - topicService = AdminClientTopicService(adminClient) + topicService = TopicService(adminClient) testTopicName = s"${info.getTestMethod.get().getName}-${Random.alphanumeric.take(10).mkString}" } @@ -105,8 +106,7 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin @Test def testCreateWithDefaults(): Unit = { - createAndWaitTopic(new TopicCommandOptions( - Array("--topic", testTopicName))) + createAndWaitTopic(new TopicCommandOptions(Array("--topic", testTopicName))) val partitions = adminClient .describeTopics(Collections.singletonList(testTopicName)) @@ -341,7 +341,7 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin def testAlterWhenTopicDoesntExist(): Unit = { // alter a topic that does not exist without --if-exists val alterOpts = new TopicCommandOptions(Array("--topic", testTopicName, "--partitions", "1")) - val topicService = AdminClientTopicService(adminClient) + val topicService = TopicService(adminClient) assertThrows(classOf[IllegalArgumentException], () => topicService.alterTopic(alterOpts)) } @@ -387,7 +387,8 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin val cleanupVal = "compact" // create the topic - val createOpts = new TopicCommandOptions(Array("--partitions", numPartitionsOriginal.toString, + val createOpts = new TopicCommandOptions(Array( + "--partitions", numPartitionsOriginal.toString, "--replication-factor", "1", "--config", cleanupKey + "=" + cleanupVal, "--topic", testTopicName)) @@ -401,7 +402,8 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin // modify the topic to add new partitions val numPartitionsModified = 3 - val alterOpts = new TopicCommandOptions(Array("--partitions", numPartitionsModified.toString, "--topic", testTopicName)) + val alterOpts = new TopicCommandOptions( + Array("--partitions", numPartitionsModified.toString, "--topic", testTopicName)) topicService.alterTopic(alterOpts) val newProps = adminZkClient.fetchEntityConfig(ConfigType.Topic, testTopicName) assertTrue(newProps.containsKey(cleanupKey), "Updated properties do not contain " + cleanupKey) @@ -436,7 +438,8 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin // Try to delete the Topic.GROUP_METADATA_TOPIC_NAME which is allowed by default. // This is a difference between the new and the old command as the old one didn't allow internal topic deletion. // If deleting internal topics is not desired, ACLS should be used to control it. - val deleteOffsetTopicOpts = new TopicCommandOptions(Array("--topic", Topic.GROUP_METADATA_TOPIC_NAME)) + val deleteOffsetTopicOpts = new TopicCommandOptions( + Array("--topic", Topic.GROUP_METADATA_TOPIC_NAME)) val deleteOffsetTopicPath = DeleteTopicsTopicZNode.path(Topic.GROUP_METADATA_TOPIC_NAME) assertFalse(zkClient.pathExists(deleteOffsetTopicPath), "Delete path for topic shouldn't exist before deletion.") topicService.deleteTopic(deleteOffsetTopicOpts) @@ -512,7 +515,8 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin // grab the console output and assert val output = TestUtils.grabConsoleOutput( - topicService.describeTopic(new TopicCommandOptions(Array("--topic", testTopicName, "--unavailable-partitions")))) + topicService.describeTopic(new TopicCommandOptions( + Array("--topic", testTopicName, "--unavailable-partitions")))) val rows = output.split("\n") assertTrue(rows(0).startsWith(s"\tTopic: $testTopicName")) assertTrue(rows(0).contains("Leader: none\tReplicas: 0\tIsr:")) @@ -703,7 +707,8 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin new TopicCommandOptions(Array("--partitions", "1", "--replication-factor", "1", "--topic", Topic.GROUP_METADATA_TOPIC_NAME))) // test describe - var output = TestUtils.grabConsoleOutput(topicService.describeTopic(new TopicCommandOptions(Array("--describe", "--exclude-internal")))) + var output = TestUtils.grabConsoleOutput(topicService.describeTopic(new TopicCommandOptions( + Array("--describe", "--exclude-internal")))) assertTrue(output.contains(testTopicName), s"Output should have contained $testTopicName") assertFalse(output.contains(Topic.GROUP_METADATA_TOPIC_NAME)) @@ -716,7 +721,7 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin @Test def testDescribeDoesNotFailWhenListingReassignmentIsUnauthorized(): Unit = { adminClient = spy(adminClient) - topicService = AdminClientTopicService(adminClient) + topicService = TopicService(adminClient) val result = AdminClientTestUtils.listPartitionReassignmentsResult( new ClusterAuthorizationException("Unauthorized")) @@ -742,7 +747,7 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin @Test def testCreateTopicDoesNotRetryThrottlingQuotaExceededException(): Unit = { val adminClient = mock(classOf[Admin]) - val topicService = AdminClientTopicService(adminClient) + val topicService = TopicService(adminClient) val result = AdminClientTestUtils.createTopicsResult(testTopicName, Errors.THROTTLING_QUOTA_EXCEEDED.exception()) when(adminClient.createTopics(any(), any())).thenReturn(result) @@ -762,7 +767,7 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin @Test def testDeleteTopicDoesNotRetryThrottlingQuotaExceededException(): Unit = { val adminClient = mock(classOf[Admin]) - val topicService = AdminClientTopicService(adminClient) + val topicService = TopicService(adminClient) val listResult = AdminClientTestUtils.listTopicsResult(testTopicName) when(adminClient.listTopics(any())).thenReturn(listResult) @@ -783,7 +788,7 @@ class TopicCommandWithAdminClientTest extends KafkaServerTestHarness with Loggin @Test def testCreatePartitionsDoesNotRetryThrottlingQuotaExceededException(): Unit = { val adminClient = mock(classOf[Admin]) - val topicService = AdminClientTopicService(adminClient) + val topicService = TopicService(adminClient) val listResult = AdminClientTestUtils.listTopicsResult(testTopicName) when(adminClient.listTopics(any())).thenReturn(listResult) diff --git a/core/src/test/scala/unit/kafka/admin/TopicCommandTest.scala b/core/src/test/scala/unit/kafka/admin/TopicCommandTest.scala index c5b396b51849a..f34e154890509 100644 --- a/core/src/test/scala/unit/kafka/admin/TopicCommandTest.scala +++ b/core/src/test/scala/unit/kafka/admin/TopicCommandTest.scala @@ -81,6 +81,26 @@ class TopicCommandTest { assertEquals("cleanup.policy=compact", opts.topicConfig.get.get(0)) } + @Test + def testCreateWithPartitionCountWithoutReplicationFactor(): Unit = { + assertCheckArgsExitCode(1, + new TopicCommandOptions( + Array("--bootstrap-server", brokerList, + "--create", + "--partitions", "2", + "--topic", topicName))) + } + + @Test + def testCreateWithReplicationFactorWithoutPartitionCount(): Unit = { + assertCheckArgsExitCode(1, + new TopicCommandOptions( + Array("--bootstrap-server", brokerList, + "--create", + "--replication-factor", "3", + "--topic", topicName))) + } + @Test def testCreateWithAssignmentAndPartitionCount(): Unit = { assertCheckArgsExitCode(1, @@ -89,7 +109,7 @@ class TopicCommandTest { "--create", "--replica-assignment", "3:0,5:1", "--partitions", "2", - "--topic", "testTopic"))) + "--topic", topicName))) } @Test @@ -100,7 +120,7 @@ class TopicCommandTest { "--create", "--replica-assignment", "3:0,5:1", "--replication-factor", "2", - "--topic", "testTopic"))) + "--topic", topicName))) } @Test diff --git a/core/src/test/scala/unit/kafka/admin/TopicCommandWithZKClientTest.scala b/core/src/test/scala/unit/kafka/admin/TopicCommandWithZKClientTest.scala deleted file mode 100644 index 92b24d58ddcfb..0000000000000 --- a/core/src/test/scala/unit/kafka/admin/TopicCommandWithZKClientTest.scala +++ /dev/null @@ -1,590 +0,0 @@ -/** - * 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 kafka.admin - -import kafka.admin.TopicCommand.{TopicCommandOptions, ZookeeperTopicService} -import kafka.server.ConfigType -import kafka.utils.{Exit, Logging, TestUtils} -import kafka.zk.{ConfigEntityChangeNotificationZNode, DeleteTopicsTopicZNode, ZooKeeperTestHarness} -import org.apache.kafka.common.TopicPartition -import org.apache.kafka.common.config.{ConfigException, ConfigResource} -import org.apache.kafka.common.errors.{InvalidPartitionsException, InvalidReplicationFactorException, TopicExistsException} -import org.apache.kafka.common.internals.Topic -import org.junit.jupiter.api.Assertions._ -import org.junit.jupiter.api.{AfterEach, BeforeEach, Test, TestInfo} - -import scala.util.Random - -class TopicCommandWithZKClientTest extends ZooKeeperTestHarness with Logging with RackAwareTest { - - private var topicService: ZookeeperTopicService = _ - private var testTopicName: String = _ - - @BeforeEach - def setup(info: TestInfo): Unit = { - topicService = ZookeeperTopicService(zkClient) - testTopicName = s"${info.getTestMethod.get().getName}-${Random.alphanumeric.take(10).mkString}" - } - - @AfterEach - def teardown(): Unit = { - if (topicService != null) - topicService.close() - } - - @Test - def testCreate(): Unit = { - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - topicService.createTopic(new TopicCommandOptions( - Array("--partitions", "2", "--replication-factor", "1", "--topic", testTopicName))) - - assertTrue(zkClient.getAllTopicsInCluster().contains(testTopicName)) - } - - @Test - def testCreateWithConfigs(): Unit = { - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - val configResource = new ConfigResource(ConfigResource.Type.TOPIC, testTopicName) - topicService.createTopic(new TopicCommandOptions( - Array("--partitions", "2", "--replication-factor", "2", "--topic", configResource.name(), "--config", "delete.retention.ms=1000"))) - - val configs = zkClient.getEntityConfigs(ConfigType.Topic, testTopicName) - assertEquals(1000, Integer.valueOf(configs.getProperty("delete.retention.ms"))) - } - - @Test - def testCreateIfNotExists(): Unit = { - // create brokers - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - val numPartitions = 1 - - // create the topic - val createOpts = new TopicCommandOptions( - Array("--partitions", numPartitions.toString, "--replication-factor", "1", "--topic", testTopicName)) - topicService.createTopic(createOpts) - - // try to re-create the topic without --if-not-exists - assertThrows(classOf[TopicExistsException], () => topicService.createTopic(createOpts)) - - // try to re-create the topic with --if-not-exists - val createNotExistsOpts = new TopicCommandOptions( - Array("--partitions", numPartitions.toString, "--replication-factor", "1", "--topic", testTopicName, "--if-not-exists")) - topicService.createTopic(createNotExistsOpts) - } - - @Test - def testCreateWithReplicaAssignment(): Unit = { - // create the topic - val createOpts = new TopicCommandOptions( - Array("--replica-assignment", "5:4,3:2,1:0", "--topic", testTopicName)) - topicService.createTopic(createOpts) - - val replicas0 = zkClient.getReplicasForPartition(new TopicPartition(testTopicName, 0)) - assertEquals(List(5, 4), replicas0) - val replicas1 = zkClient.getReplicasForPartition(new TopicPartition(testTopicName, 1)) - assertEquals(List(3, 2), replicas1) - val replicas2 = zkClient.getReplicasForPartition(new TopicPartition(testTopicName, 2)) - assertEquals(List(1, 0), replicas2) - } - - @Test - def testCreateWithInvalidReplicationFactor(): Unit = { - val brokers = List(0) - TestUtils.createBrokersInZk(zkClient, brokers) - - assertThrows(classOf[InvalidReplicationFactorException], - () => topicService.createTopic(new TopicCommandOptions( - Array("--partitions", "2", "--replication-factor", (Short.MaxValue+1).toString, "--topic", testTopicName)))) - } - - @Test - def testCreateWithNegativeReplicationFactor(): Unit = { - val brokers = List(0) - TestUtils.createBrokersInZk(zkClient, brokers) - - assertThrows(classOf[InvalidReplicationFactorException], - () => topicService.createTopic(new TopicCommandOptions( - Array("--partitions", "2", "--replication-factor", "-1", "--topic", testTopicName)))) - } - - @Test - def testCreateWithNegativePartitionCount(): Unit = { - val brokers = List(0) - TestUtils.createBrokersInZk(zkClient, brokers) - - assertThrows(classOf[InvalidPartitionsException], - () => topicService.createTopic(new TopicCommandOptions( - Array("--partitions", "-1", "--replication-factor", "1", "--topic", testTopicName)))) - } - - @Test - def testInvalidTopicLevelConfig(): Unit = { - val brokers = List(0) - TestUtils.createBrokersInZk(zkClient, brokers) - - val createOpts = new TopicCommandOptions( - Array("--partitions", "1", "--replication-factor", "1", "--topic", testTopicName, - "--config", "message.timestamp.type=boom")) - assertThrows(classOf[ConfigException], () => topicService.createTopic(createOpts)) - - // try to create the topic with another invalid config - val createOpts2 = new TopicCommandOptions( - Array("--partitions", "1", "--replication-factor", "1", "--topic", testTopicName, - "--config", "message.format.version=boom")) - assertThrows(classOf[ConfigException], () => topicService.createTopic(createOpts2)) - } - - @Test - def testListTopics(): Unit = { - val brokers = List(0) - TestUtils.createBrokersInZk(zkClient, brokers) - - topicService.createTopic(new TopicCommandOptions( - Array("--partitions", "1", "--replication-factor", "1", "--topic", testTopicName))) - - val output = TestUtils.grabConsoleOutput( - topicService.listTopics(new TopicCommandOptions(Array()))) - - assertTrue(output.contains(testTopicName)) - } - - @Test - def testListTopicsWithIncludeList(): Unit = { - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - val topic1 = "kafka.testTopic1" - val topic2 = "kafka.testTopic2" - val topic3 = "oooof.testTopic1" - adminZkClient.createTopic(topic1, 2, 2) - adminZkClient.createTopic(topic2, 2, 2) - adminZkClient.createTopic(topic3, 2, 2) - - val output = TestUtils.grabConsoleOutput( - topicService.listTopics(new TopicCommandOptions(Array("--topic", "kafka.*")))) - - assertTrue(output.contains(topic1)) - assertTrue(output.contains(topic2)) - assertFalse(output.contains(topic3)) - } - - @Test - def testListTopicsWithExcludeInternal(): Unit = { - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - adminZkClient.createTopic(testTopicName, 2, 2) - adminZkClient.createTopic(Topic.GROUP_METADATA_TOPIC_NAME, 2, 2) - - val output = TestUtils.grabConsoleOutput( - topicService.listTopics(new TopicCommandOptions(Array("--exclude-internal")))) - - assertTrue(output.contains(testTopicName)) - assertFalse(output.contains(Topic.GROUP_METADATA_TOPIC_NAME)) - } - - @Test - def testAlterPartitionCount(): Unit = { - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - adminZkClient.createTopic(testTopicName, 2, 2) - - topicService.alterTopic(new TopicCommandOptions( - Array("--topic", testTopicName, "--partitions", "3"))) - - assertEquals(3, zkClient.getPartitionsForTopics(Set(testTopicName))(testTopicName).size) - } - - @Test - def testAlterAssignment(): Unit = { - val brokers = List(0, 1, 2, 3, 4, 5) - TestUtils.createBrokersInZk(zkClient, brokers) - - adminZkClient.createTopic(testTopicName, 2, 2) - - topicService.alterTopic(new TopicCommandOptions( - Array("--topic", testTopicName, "--replica-assignment", "5:3,3:1,4:2", "--partitions", "3"))) - - val replicas0 = zkClient.getReplicasForPartition(new TopicPartition(testTopicName, 2)) - assertEquals(3, zkClient.getPartitionsForTopics(Set(testTopicName))(testTopicName).size) - assertEquals(List(4,2), replicas0) - } - - @Test - def testAlterWithInvalidPartitionCount(): Unit = { - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - topicService.createTopic(new TopicCommandOptions( - Array("--partitions", "1", "--replication-factor", "1", "--topic", testTopicName))) - - assertThrows(classOf[InvalidPartitionsException], - () => topicService.alterTopic(new TopicCommandOptions( - Array("--partitions", "-1", "--topic", testTopicName)))) - } - - @Test - def testAlterIfExists(): Unit = { - // create brokers - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - // alter a topic that does not exist without --if-exists - val alterOpts = new TopicCommandOptions(Array("--topic", testTopicName, "--partitions", "1")) - assertThrows(classOf[IllegalArgumentException], () => topicService.alterTopic(alterOpts)) - - // alter a topic that does not exist with --if-exists - val alterExistsOpts = new TopicCommandOptions(Array("--topic", testTopicName, "--partitions", "1", "--if-exists")) - topicService.alterTopic(alterExistsOpts) - } - - @Test - def testAlterConfigs(): Unit = { - // create brokers - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - topicService.createTopic(new TopicCommandOptions( - Array("--partitions", "1", "--replication-factor", "1", "--topic", testTopicName))) - - topicService.alterTopic(new TopicCommandOptions( - Array("--topic", testTopicName, "--config", "cleanup.policy=compact"))) - - val output = TestUtils.grabConsoleOutput( - topicService.describeTopic(new TopicCommandOptions(Array("--topic", testTopicName)))) - assertTrue(output.contains("Configs: cleanup.policy=compact"), "The output should contain the modified config") - - topicService.alterTopic(new TopicCommandOptions( - Array("--topic", testTopicName, "--config", "cleanup.policy=delete"))) - - val output2 = TestUtils.grabConsoleOutput( - topicService.describeTopic(new TopicCommandOptions(Array("--topic", testTopicName)))) - assertTrue(output2.contains("Configs: cleanup.policy=delete"), "The output should contain the modified config") - } - - @Test - def testConfigPreservationAcrossPartitionAlteration(): Unit = { - val numPartitionsOriginal = 1 - val cleanupKey = "cleanup.policy" - val cleanupVal = "compact" - // create brokers - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - // create the topic - val createOpts = new TopicCommandOptions(Array("--partitions", numPartitionsOriginal.toString, - "--replication-factor", "1", - "--config", cleanupKey + "=" + cleanupVal, - "--topic", testTopicName)) - topicService.createTopic(createOpts) - val props = adminZkClient.fetchEntityConfig(ConfigType.Topic, testTopicName) - assertTrue(props.containsKey(cleanupKey), "Properties after creation don't contain " + cleanupKey) - assertTrue(props.getProperty(cleanupKey).equals(cleanupVal), "Properties after creation have incorrect value") - - // pre-create the topic config changes path to avoid a NoNodeException - zkClient.makeSurePersistentPathExists(ConfigEntityChangeNotificationZNode.path) - - // modify the topic to add new partitions - val numPartitionsModified = 3 - val alterOpts = new TopicCommandOptions(Array("--partitions", numPartitionsModified.toString, "--topic", testTopicName)) - topicService.alterTopic(alterOpts) - val newProps = adminZkClient.fetchEntityConfig(ConfigType.Topic, testTopicName) - assertTrue(newProps.containsKey(cleanupKey), "Updated properties do not contain " + cleanupKey) - assertTrue(newProps.getProperty(cleanupKey).equals(cleanupVal), "Updated properties have incorrect value") - } - - @Test - def testTopicDeletion(): Unit = { - - val numPartitionsOriginal = 1 - - // create brokers - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - // create the NormalTopic - val createOpts = new TopicCommandOptions(Array("--partitions", numPartitionsOriginal.toString, - "--replication-factor", "1", - "--topic", testTopicName)) - topicService.createTopic(createOpts) - - // delete the NormalTopic - val deleteOpts = new TopicCommandOptions(Array("--topic", testTopicName)) - val deletePath = DeleteTopicsTopicZNode.path(testTopicName) - assertFalse(zkClient.pathExists(deletePath), "Delete path for topic shouldn't exist before deletion.") - topicService.deleteTopic(deleteOpts) - assertTrue(zkClient.pathExists(deletePath), "Delete path for topic should exist after deletion.") - - // create the offset topic - val createOffsetTopicOpts = new TopicCommandOptions(Array("--partitions", numPartitionsOriginal.toString, - "--replication-factor", "1", - "--topic", Topic.GROUP_METADATA_TOPIC_NAME)) - topicService.createTopic(createOffsetTopicOpts) - - // try to delete the Topic.GROUP_METADATA_TOPIC_NAME and make sure it doesn't - val deleteOffsetTopicOpts = new TopicCommandOptions(Array("--topic", Topic.GROUP_METADATA_TOPIC_NAME)) - val deleteOffsetTopicPath = DeleteTopicsTopicZNode.path(Topic.GROUP_METADATA_TOPIC_NAME) - assertFalse(zkClient.pathExists(deleteOffsetTopicPath), "Delete path for topic shouldn't exist before deletion.") - assertThrows(classOf[AdminOperationException], () => topicService.deleteTopic(deleteOffsetTopicOpts)) - assertFalse(zkClient.pathExists(deleteOffsetTopicPath), "Delete path for topic shouldn't exist after deletion.") - } - - @Test - def testDeleteIfExists(): Unit = { - // create brokers - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - // delete a topic that does not exist without --if-exists - val deleteOpts = new TopicCommandOptions(Array("--topic", testTopicName)) - assertThrows(classOf[IllegalArgumentException], () => topicService.deleteTopic(deleteOpts)) - - // delete a topic that does not exist with --if-exists - val deleteExistsOpts = new TopicCommandOptions(Array("--topic", testTopicName, "--if-exists")) - topicService.deleteTopic(deleteExistsOpts) - } - - @Test - def testDeleteInternalTopic(): Unit = { - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - // create the offset topic - val createOffsetTopicOpts = new TopicCommandOptions(Array("--partitions", "1", - "--replication-factor", "1", - "--topic", Topic.GROUP_METADATA_TOPIC_NAME)) - topicService.createTopic(createOffsetTopicOpts) - - val deleteOffsetTopicOpts = new TopicCommandOptions(Array("--topic", Topic.GROUP_METADATA_TOPIC_NAME)) - val deleteOffsetTopicPath = DeleteTopicsTopicZNode.path(Topic.GROUP_METADATA_TOPIC_NAME) - assertFalse(zkClient.pathExists(deleteOffsetTopicPath), "Delete path for topic shouldn't exist before deletion.") - assertThrows(classOf[AdminOperationException], () => topicService.deleteTopic(deleteOffsetTopicOpts)) - } - - @Test - def testDescribeIfTopicNotExists(): Unit = { - // create brokers - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - // describe topic that does not exist - val describeOpts = new TopicCommandOptions(Array("--topic", testTopicName)) - assertThrows(classOf[IllegalArgumentException], () => topicService.describeTopic(describeOpts)) - - // describe all topics - val describeOptsAllTopics = new TopicCommandOptions(Array()) - // should not throw any error - topicService.describeTopic(describeOptsAllTopics) - - // describe topic that does not exist with --if-exists - val describeOptsWithExists = new TopicCommandOptions(Array("--topic", testTopicName, "--if-exists")) - // should not throw any error - topicService.describeTopic(describeOptsWithExists) - } - - @Test - def testCreateAlterTopicWithRackAware(): Unit = { - val rackInfo = Map(0 -> "rack1", 1 -> "rack2", 2 -> "rack2", 3 -> "rack1", 4 -> "rack3", 5 -> "rack3") - TestUtils.createBrokersInZk(toBrokerMetadata(rackInfo), zkClient) - - val numPartitions = 18 - val replicationFactor = 3 - val createOpts = new TopicCommandOptions(Array( - "--partitions", numPartitions.toString, - "--replication-factor", replicationFactor.toString, - "--topic", testTopicName)) - topicService.createTopic(createOpts) - - var assignment = zkClient.getReplicaAssignmentForTopics(Set(testTopicName)).map { case (tp, replicas) => - tp.partition -> replicas - } - checkReplicaDistribution(assignment, rackInfo, rackInfo.size, numPartitions, replicationFactor) - - val alteredNumPartitions = 36 - // verify that adding partitions will also be rack aware - val alterOpts = new TopicCommandOptions(Array( - "--partitions", alteredNumPartitions.toString, - "--topic", testTopicName)) - topicService.alterTopic(alterOpts) - assignment = zkClient.getReplicaAssignmentForTopics(Set(testTopicName)).map { case (tp, replicas) => - tp.partition -> replicas - } - checkReplicaDistribution(assignment, rackInfo, rackInfo.size, alteredNumPartitions, replicationFactor) - } - - @Test - def testDescribe(): Unit = { - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - adminZkClient.createTopic(testTopicName, 2, 2) - val output = TestUtils.grabConsoleOutput( - topicService.describeTopic(new TopicCommandOptions(Array("--topic", testTopicName)))) - val rows = output.split("\n") - assertEquals(3, rows.size) - assertTrue(rows(0).startsWith(s"Topic: $testTopicName")) - } - - @Test - def testDescribeReportOverriddenConfigs(): Unit = { - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - val config = "file.delete.delay.ms=1000" - val configResource = new ConfigResource(ConfigResource.Type.TOPIC, testTopicName) - topicService.createTopic(new TopicCommandOptions( - Array("--partitions", "2", "--replication-factor", "2", "--topic", configResource.name(), "--config", config))) - val output = TestUtils.grabConsoleOutput( - topicService.describeTopic(new TopicCommandOptions(Array()))) - assertTrue(output.contains(config)) - } - - @Test - def testDescribeAndListTopicsMarkedForDeletion(): Unit = { - val brokers = List(0) - val markedForDeletionDescribe = "MarkedForDeletion" - val markedForDeletionList = "marked for deletion" - TestUtils.createBrokersInZk(zkClient, brokers) - - val createOpts = new TopicCommandOptions(Array("--partitions", "1", "--replication-factor", "1", "--topic", testTopicName)) - topicService.createTopic(createOpts) - - // delete the broker first, so when we attempt to delete the topic it gets into "marked for deletion" - TestUtils.deleteBrokersInZk(zkClient, brokers) - topicService.deleteTopic(new TopicCommandOptions(Array("--topic", testTopicName))) - - // Test describe topics - def describeTopicsWithConfig(): Unit = { - topicService.describeTopic(new TopicCommandOptions(Array("--describe"))) - } - val outputWithConfig = TestUtils.grabConsoleOutput(describeTopicsWithConfig()) - assertTrue(outputWithConfig.contains(testTopicName) && outputWithConfig.contains(markedForDeletionDescribe)) - - def describeTopicsNoConfig(): Unit = { - topicService.describeTopic(new TopicCommandOptions(Array("--describe", "--unavailable-partitions"))) - } - val outputNoConfig = TestUtils.grabConsoleOutput(describeTopicsNoConfig()) - assertTrue(outputNoConfig.contains(testTopicName) && outputNoConfig.contains(markedForDeletionDescribe)) - - // Test list topics - def listTopics(): Unit = { - topicService.listTopics(new TopicCommandOptions(Array("--list"))) - } - val output = TestUtils.grabConsoleOutput(listTopics()) - assertTrue(output.contains(testTopicName) && output.contains(markedForDeletionList)) - } - - @Test - def testDescribeAndListTopicsWithoutInternalTopics(): Unit = { - val brokers = List(0) - TestUtils.createBrokersInZk(zkClient, brokers) - - topicService.createTopic( - new TopicCommandOptions(Array("--partitions", "1", "--replication-factor", "1", "--topic", testTopicName))) - // create a internal topic - topicService.createTopic( - new TopicCommandOptions(Array("--partitions", "1", "--replication-factor", "1", "--topic", Topic.GROUP_METADATA_TOPIC_NAME))) - - // test describe - var output = TestUtils.grabConsoleOutput(topicService.describeTopic( - new TopicCommandOptions(Array("--describe", "--exclude-internal")))) - assertTrue(output.contains(testTopicName)) - assertFalse(output.contains(Topic.GROUP_METADATA_TOPIC_NAME)) - - // test list - output = TestUtils.grabConsoleOutput(topicService.listTopics( - new TopicCommandOptions(Array("--list", "--exclude-internal")))) - assertTrue(output.contains(testTopicName)) - assertFalse(output.contains(Topic.GROUP_METADATA_TOPIC_NAME)) - } - - @Test - def testTopicOperationsWithRegexSymbolInTopicName(): Unit = { - val topic1 = "test.topic" - val topic2 = "test-topic" - val escapedTopic = "\"test\\.topic\"" - val unescapedTopic = "test.topic" - val numPartitionsOriginal = 1 - - // create brokers - val brokers = List(0, 1, 2) - TestUtils.createBrokersInZk(zkClient, brokers) - - // create the topics - val createOpts = new TopicCommandOptions(Array("--partitions", numPartitionsOriginal.toString, - "--replication-factor", "1", "--topic", topic1)) - topicService.createTopic(createOpts) - val createOpts2 = new TopicCommandOptions(Array("--partitions", numPartitionsOriginal.toString, - "--replication-factor", "1", "--topic", topic2)) - topicService.createTopic(createOpts2) - - val escapedCommandOpts = new TopicCommandOptions(Array("--topic", escapedTopic)) - val unescapedCommandOpts = new TopicCommandOptions(Array("--topic", unescapedTopic)) - - // topic actions with escaped regex do not affect 'test-topic' - // topic actions with unescaped topic affect 'test-topic' - - assertFalse(TestUtils.grabConsoleOutput(topicService.describeTopic(escapedCommandOpts)).contains(topic2)) - assertTrue(TestUtils.grabConsoleOutput(topicService.describeTopic(unescapedCommandOpts)).contains(topic2)) - - assertFalse(TestUtils.grabConsoleOutput(topicService.deleteTopic(escapedCommandOpts)).contains(topic2)) - assertTrue(TestUtils.grabConsoleOutput(topicService.deleteTopic(unescapedCommandOpts)).contains(topic2)) - } - - @Test - def testAlterInternalTopicPartitionCount(): Unit = { - val brokers = List(0) - TestUtils.createBrokersInZk(zkClient, brokers) - - // create internal topics - adminZkClient.createTopic(Topic.GROUP_METADATA_TOPIC_NAME, 1, 1) - adminZkClient.createTopic(Topic.TRANSACTION_STATE_TOPIC_NAME, 1, 1) - - def expectAlterInternalTopicPartitionCountFailed(topic: String): Unit = { - assertThrows(classOf[IllegalArgumentException], () => topicService.alterTopic(new TopicCommandOptions( - Array("--topic", topic, "--partitions", "2")))) - } - expectAlterInternalTopicPartitionCountFailed(Topic.GROUP_METADATA_TOPIC_NAME) - expectAlterInternalTopicPartitionCountFailed(Topic.TRANSACTION_STATE_TOPIC_NAME) - } - - @Test - def testCreateWithUnspecifiedReplicationFactorAndPartitionsWithZkClient(): Unit = { - assertExitCode(1, () => - new TopicCommandOptions(Array("--create", "--zookeeper", "zk", "--topic", testTopicName)).checkArgs() - ) - } - - def assertExitCode(expected: Int, method: () => Unit): Unit = { - def mockExitProcedure(exitCode: Int, exitMessage: Option[String]): Nothing = { - assertEquals(expected, exitCode) - throw new RuntimeException - } - Exit.setExitProcedure(mockExitProcedure) - try { - assertThrows(classOf[RuntimeException], () => method()) - } finally { - Exit.resetExitProcedure() - } - } -} diff --git a/core/src/test/scala/unit/kafka/cluster/PartitionLockTest.scala b/core/src/test/scala/unit/kafka/cluster/PartitionLockTest.scala index 0cbeec9798c13..8dc37d4e4ad1b 100644 --- a/core/src/test/scala/unit/kafka/cluster/PartitionLockTest.scala +++ b/core/src/test/scala/unit/kafka/cluster/PartitionLockTest.scala @@ -20,7 +20,6 @@ package kafka.cluster import java.util.Properties import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent._ - import kafka.api.ApiVersion import kafka.log._ import kafka.server._ @@ -336,10 +335,11 @@ class PartitionLockTest extends Logging { } private def append(partition: Partition, numRecords: Int, followerQueues: Seq[ArrayBlockingQueue[MemoryRecords]]): Unit = { + val requestLocal = RequestLocal.withThreadConfinedCaching (0 until numRecords).foreach { _ => val batch = TestUtils.records(records = List(new SimpleRecord("k1".getBytes, "v1".getBytes), new SimpleRecord("k2".getBytes, "v2".getBytes))) - partition.appendRecordsToLeader(batch, origin = AppendOrigin.Client, requiredAcks = 0) + partition.appendRecordsToLeader(batch, origin = AppendOrigin.Client, requiredAcks = 0, requestLocal) followerQueues.foreach(_.put(batch)) } } @@ -385,11 +385,12 @@ class PartitionLockTest extends Logging { leaderEpochCache, producerStateManager, logDirFailureChannel, - topicId = None, + _topicId = None, keepPartitionMetadataFile = true) { - override def appendAsLeader(records: MemoryRecords, leaderEpoch: Int, origin: AppendOrigin, interBrokerProtocolVersion: ApiVersion): LogAppendInfo = { - val appendInfo = super.appendAsLeader(records, leaderEpoch, origin, interBrokerProtocolVersion) + override def appendAsLeader(records: MemoryRecords, leaderEpoch: Int, origin: AppendOrigin, + interBrokerProtocolVersion: ApiVersion, requestLocal: RequestLocal): LogAppendInfo = { + val appendInfo = super.appendAsLeader(records, leaderEpoch, origin, interBrokerProtocolVersion, requestLocal) appendSemaphore.acquire() appendInfo } diff --git a/core/src/test/scala/unit/kafka/cluster/PartitionTest.scala b/core/src/test/scala/unit/kafka/cluster/PartitionTest.scala index 703071568a0c0..2b6d7ff8dd03f 100644 --- a/core/src/test/scala/unit/kafka/cluster/PartitionTest.scala +++ b/core/src/test/scala/unit/kafka/cluster/PartitionTest.scala @@ -72,7 +72,7 @@ class PartitionTest extends AbstractPartitionTest { assertEquals(17L, log.logEndOffset) val leaderEpoch = 10 - val partition = setupPartitionWithMocks(leaderEpoch = leaderEpoch, isLeader = true, log = log) + val partition = setupPartitionWithMocks(leaderEpoch = leaderEpoch, isLeader = true) def epochEndOffset(epoch: Int, endOffset: Long): FetchResponseData.EpochEndOffset = { new FetchResponseData.EpochEndOffset() @@ -143,7 +143,7 @@ class PartitionTest extends AbstractPartitionTest { ), leaderEpoch = 5) assertEquals(4, log.logEndOffset) - val partition = setupPartitionWithMocks(leaderEpoch = leaderEpoch, isLeader = true, log = log) + val partition = setupPartitionWithMocks(leaderEpoch = leaderEpoch, isLeader = true) assertEquals(Some(4), partition.leaderLogIfLocal.map(_.logEndOffset)) val epochEndOffset = partition.lastOffsetForLeaderEpoch(currentLeaderEpoch = Optional.of[Integer](leaderEpoch), @@ -171,7 +171,7 @@ class PartitionTest extends AbstractPartitionTest { ), leaderEpoch = 5) assertEquals(4, log.logEndOffset) - val partition = setupPartitionWithMocks(leaderEpoch = leaderEpoch, isLeader = true, log = log) + val partition = setupPartitionWithMocks(leaderEpoch = leaderEpoch, isLeader = true) assertEquals(Some(4), partition.leaderLogIfLocal.map(_.logEndOffset)) assertEquals(None, log.latestEpoch) @@ -592,10 +592,11 @@ class PartitionTest extends AbstractPartitionTest { assertEquals(leaderEpoch, partition.getLeaderEpoch, "Current leader epoch") assertEquals(Set[Integer](leader, follower2), partition.isrState.isr, "ISR") + val requestLocal = RequestLocal.withThreadConfinedCaching // after makeLeader(() call, partition should know about all the replicas // append records with initial leader epoch - partition.appendRecordsToLeader(batch1, origin = AppendOrigin.Client, requiredAcks = 0) - partition.appendRecordsToLeader(batch2, origin = AppendOrigin.Client, requiredAcks = 0) + partition.appendRecordsToLeader(batch1, origin = AppendOrigin.Client, requiredAcks = 0, requestLocal) + partition.appendRecordsToLeader(batch2, origin = AppendOrigin.Client, requiredAcks = 0, requestLocal) assertEquals(partition.localLogOrException.logStartOffset, partition.localLogOrException.highWatermark, "Expected leader's HW not move") @@ -733,8 +734,7 @@ class PartitionTest extends AbstractPartitionTest { } private def setupPartitionWithMocks(leaderEpoch: Int, - isLeader: Boolean, - log: Log = logManager.getOrCreateLog(topicPartition, topicId = None)): Partition = { + isLeader: Boolean): Partition = { partition.createLogIfNotExists(isNew = false, isFutureReplica = false, offsetCheckpoints, None) val controllerEpoch = 0 @@ -840,7 +840,7 @@ class PartitionTest extends AbstractPartitionTest { new SimpleRecord("k2".getBytes, "v2".getBytes), new SimpleRecord("k3".getBytes, "v3".getBytes)), baseOffset = 0L) - partition.appendRecordsToLeader(records, origin = AppendOrigin.Client, requiredAcks = 0) + partition.appendRecordsToLeader(records, origin = AppendOrigin.Client, requiredAcks = 0, RequestLocal.withThreadConfinedCaching) def fetchLatestOffset(isolationLevel: Option[IsolationLevel]): TimestampAndOffset = { val res = partition.fetchOffsetForTimestamp(ListOffsetsRequest.LATEST_TIMESTAMP, @@ -955,11 +955,13 @@ class PartitionTest extends AbstractPartitionTest { assertEquals(leaderEpoch, partition.getLeaderEpoch, "Current leader epoch") assertEquals(Set[Integer](leader, follower2), partition.isrState.isr, "ISR") + val requestLocal = RequestLocal.withThreadConfinedCaching + // after makeLeader(() call, partition should know about all the replicas // append records with initial leader epoch val lastOffsetOfFirstBatch = partition.appendRecordsToLeader(batch1, origin = AppendOrigin.Client, - requiredAcks = 0).lastOffset - partition.appendRecordsToLeader(batch2, origin = AppendOrigin.Client, requiredAcks = 0) + requiredAcks = 0, requestLocal).lastOffset + partition.appendRecordsToLeader(batch2, origin = AppendOrigin.Client, requiredAcks = 0, requestLocal) assertEquals(partition.localLogOrException.logStartOffset, partition.log.get.highWatermark, "Expected leader's HW not move") // let the follower in ISR move leader's HW to move further but below LEO @@ -1000,7 +1002,7 @@ class PartitionTest extends AbstractPartitionTest { val currentLeaderEpochStartOffset = partition.localLogOrException.logEndOffset // append records with the latest leader epoch - partition.appendRecordsToLeader(batch3, origin = AppendOrigin.Client, requiredAcks = 0) + partition.appendRecordsToLeader(batch3, origin = AppendOrigin.Client, requiredAcks = 0, requestLocal) // fetch from follower not in ISR from log start offset should not add this follower to ISR updateFollowerFetchState(follower1, LogOffsetMetadata(0)) @@ -2046,7 +2048,7 @@ class PartitionTest extends AbstractPartitionTest { leaderEpochCache, producerStateManager, logDirFailureChannel, - topicId = None, + _topicId = None, keepPartitionMetadataFile = true) { override def appendAsFollower(records: MemoryRecords): LogAppendInfo = { diff --git a/core/src/test/scala/unit/kafka/coordinator/AbstractCoordinatorConcurrencyTest.scala b/core/src/test/scala/unit/kafka/coordinator/AbstractCoordinatorConcurrencyTest.scala index 14e0f3fd7fd7e..6dfb396e9bad4 100644 --- a/core/src/test/scala/unit/kafka/coordinator/AbstractCoordinatorConcurrencyTest.scala +++ b/core/src/test/scala/unit/kafka/coordinator/AbstractCoordinatorConcurrencyTest.scala @@ -21,7 +21,6 @@ import java.util.concurrent.{ConcurrentHashMap, Executors} import java.util.{Collections, Random} import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.Lock - import kafka.coordinator.AbstractCoordinatorConcurrencyTest._ import kafka.log.{AppendOrigin, Log} import kafka.server._ @@ -160,8 +159,10 @@ object AbstractCoordinatorConcurrencyTest { class TestReplicaManager extends ReplicaManager( null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, None, null, null) { + @volatile var logs: mutable.Map[TopicPartition, (Log, Long)] = _ var producePurgatory: DelayedOperationPurgatory[DelayedProduce] = _ var watchKeys: mutable.Set[TopicPartitionOperationKey] = _ + def createDelayedProducePurgatory(timer: MockTimer): Unit = { producePurgatory = new DelayedOperationPurgatory[DelayedProduce]("Produce", timer, 1, reaperEnabled = false) watchKeys = Collections.newSetFromMap(new ConcurrentHashMap[TopicPartitionOperationKey, java.lang.Boolean]()).asScala @@ -176,7 +177,8 @@ object AbstractCoordinatorConcurrencyTest { entriesPerPartition: Map[TopicPartition, MemoryRecords], responseCallback: Map[TopicPartition, PartitionResponse] => Unit, delayedProduceLock: Option[Lock] = None, - processingStatsCallback: Map[TopicPartition, RecordConversionStats] => Unit = _ => ()): Unit = { + processingStatsCallback: Map[TopicPartition, RecordConversionStats] => Unit = _ => (), + requestLocal: RequestLocal = RequestLocal.NoCaching): Unit = { if (entriesPerPartition.isEmpty) return @@ -204,20 +206,24 @@ object AbstractCoordinatorConcurrencyTest { watchKeys ++= producerRequestKeys producePurgatory.tryCompleteElseWatch(delayedProduce, producerRequestKeys) } + override def getMagic(topicPartition: TopicPartition): Option[Byte] = { Some(RecordBatch.MAGIC_VALUE_V2) } - @volatile var logs: mutable.Map[TopicPartition, (Log, Long)] = _ + def getOrCreateLogs(): mutable.Map[TopicPartition, (Log, Long)] = { if (logs == null) logs = mutable.Map[TopicPartition, (Log, Long)]() logs } + def updateLog(topicPartition: TopicPartition, log: Log, endOffset: Long): Unit = { getOrCreateLogs().put(topicPartition, (log, endOffset)) } + override def getLog(topicPartition: TopicPartition): Option[Log] = getOrCreateLogs().get(topicPartition).map(l => l._1) + override def getLogEndOffset(topicPartition: TopicPartition): Option[Long] = getOrCreateLogs().get(topicPartition).map(l => l._2) } diff --git a/core/src/test/scala/unit/kafka/coordinator/group/GroupCoordinatorTest.scala b/core/src/test/scala/unit/kafka/coordinator/group/GroupCoordinatorTest.scala index 39826894c8f1a..cf14a36109aff 100644 --- a/core/src/test/scala/unit/kafka/coordinator/group/GroupCoordinatorTest.scala +++ b/core/src/test/scala/unit/kafka/coordinator/group/GroupCoordinatorTest.scala @@ -18,9 +18,8 @@ package kafka.coordinator.group import java.util.Optional - import kafka.common.OffsetAndMetadata -import kafka.server.{DelayedOperationPurgatory, HostedPartition, KafkaConfig, ReplicaManager} +import kafka.server.{DelayedOperationPurgatory, HostedPartition, KafkaConfig, ReplicaManager, RequestLocal} import kafka.utils._ import kafka.utils.timer.MockTimer import org.apache.kafka.common.TopicPartition @@ -29,9 +28,9 @@ import org.apache.kafka.common.record.{MemoryRecords, RecordBatch} import org.apache.kafka.common.requests.ProduceResponse.PartitionResponse import org.apache.kafka.common.requests.{JoinGroupRequest, OffsetCommitRequest, OffsetFetchResponse, TransactionResult} import org.easymock.{Capture, EasyMock, IAnswer} + import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock - import kafka.cluster.Partition import kafka.log.AppendOrigin import kafka.zk.KafkaZkClient @@ -189,7 +188,9 @@ class GroupCoordinatorTest { EasyMock.reset(replicaManager) EasyMock.expect(replicaManager.getLog(otherGroupMetadataTopicPartition)).andReturn(None) EasyMock.replay(replicaManager) - groupCoordinator.groupManager.loadGroupsAndOffsets(otherGroupMetadataTopicPartition, group => {}, 0L) + // Call removeGroupsAndOffsets so that partition removed from loadingPartitions + groupCoordinator.groupManager.removeGroupsAndOffsets(otherGroupMetadataTopicPartition, Some(1), group => {}) + groupCoordinator.groupManager.loadGroupsAndOffsets(otherGroupMetadataTopicPartition, 1, group => {}, 0L) assertEquals(Errors.NONE, groupCoordinator.handleDescribeGroup(otherGroupId)._1) } @@ -3421,7 +3422,8 @@ class GroupCoordinatorTest { @Test def testDeleteOffsetOfNonExistingGroup(): Unit = { val tp = new TopicPartition("foo", 0) - val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(tp)) + val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(tp), + RequestLocal.NoCaching) assertEquals(Errors.GROUP_ID_NOT_FOUND, groupError) assertTrue(topics.isEmpty) @@ -3432,7 +3434,8 @@ class GroupCoordinatorTest { val memberId = JoinGroupRequest.UNKNOWN_MEMBER_ID dynamicJoinGroup(groupId, memberId, "My Protocol", protocols) val tp = new TopicPartition("foo", 0) - val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(tp)) + val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(tp), + RequestLocal.NoCaching) assertEquals(Errors.NON_EMPTY_GROUP, groupError) assertTrue(topics.isEmpty) @@ -3476,7 +3479,8 @@ class GroupCoordinatorTest { EasyMock.expect(replicaManager.onlinePartition(groupTopicPartition)).andStubReturn(Some(partition)) EasyMock.replay(replicaManager, partition) - val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(t1p0)) + val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(t1p0), + RequestLocal.NoCaching) assertEquals(Errors.NONE, groupError) assertEquals(1, topics.size) @@ -3505,7 +3509,8 @@ class GroupCoordinatorTest { Map(tp -> offset)) assertEquals(Errors.NONE, validOffsetCommitResult(tp)) - val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(tp)) + val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(tp), + RequestLocal.NoCaching) assertEquals(Errors.NONE, groupError) assertEquals(1, topics.size) @@ -3519,7 +3524,8 @@ class GroupCoordinatorTest { groupCoordinator.groupManager.addGroup(group) val tp = new TopicPartition("foo", 0) - val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(tp)) + val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(tp), + RequestLocal.NoCaching) assertEquals(Errors.GROUP_ID_NOT_FOUND, groupError) assertTrue(topics.isEmpty) @@ -3562,7 +3568,8 @@ class GroupCoordinatorTest { EasyMock.expect(replicaManager.onlinePartition(groupTopicPartition)).andStubReturn(Some(partition)) EasyMock.replay(replicaManager, partition) - val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(t1p0)) + val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(t1p0), + RequestLocal.NoCaching) assertEquals(Errors.NONE, groupError) assertEquals(1, topics.size) @@ -3609,7 +3616,8 @@ class GroupCoordinatorTest { EasyMock.expect(replicaManager.onlinePartition(groupTopicPartition)).andStubReturn(Some(partition)) EasyMock.replay(replicaManager, partition) - val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(t1p0, t2p0)) + val (groupError, topics) = groupCoordinator.handleDeleteOffsets(groupId, Seq(t1p0, t2p0), + RequestLocal.NoCaching) assertEquals(Errors.NONE, groupError) assertEquals(2, topics.size) @@ -3789,12 +3797,14 @@ class GroupCoordinatorTest { EasyMock.anyObject().asInstanceOf[Map[TopicPartition, MemoryRecords]], EasyMock.capture(capturedArgument), EasyMock.anyObject().asInstanceOf[Option[ReentrantLock]], - EasyMock.anyObject())).andAnswer(new IAnswer[Unit] { - override def answer = capturedArgument.getValue.apply( + EasyMock.anyObject(), + EasyMock.anyObject() + )).andAnswer(new IAnswer[Unit] { + override def answer: Unit = capturedArgument.getValue.apply( Map(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, groupPartitionId) -> new PartitionResponse(appendRecordError, 0L, RecordBatch.NO_TIMESTAMP, 0L) - ) - )}) + )) + }) EasyMock.expect(replicaManager.getMagic(EasyMock.anyObject())).andReturn(Some(RecordBatch.MAGIC_VALUE_V1)).anyTimes() EasyMock.replay(replicaManager) @@ -3821,6 +3831,7 @@ class GroupCoordinatorTest { EasyMock.anyObject().asInstanceOf[Map[TopicPartition, MemoryRecords]], EasyMock.capture(capturedArgument), EasyMock.anyObject().asInstanceOf[Option[ReentrantLock]], + EasyMock.anyObject(), EasyMock.anyObject())).andAnswer(new IAnswer[Unit] { override def answer = capturedArgument.getValue.apply( Map(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, groupPartitionId) -> @@ -3963,6 +3974,7 @@ class GroupCoordinatorTest { EasyMock.anyObject().asInstanceOf[Map[TopicPartition, MemoryRecords]], EasyMock.capture(capturedArgument), EasyMock.anyObject().asInstanceOf[Option[ReentrantLock]], + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(new IAnswer[Unit] { override def answer = capturedArgument.getValue.apply( @@ -3996,6 +4008,7 @@ class GroupCoordinatorTest { EasyMock.anyObject().asInstanceOf[Map[TopicPartition, MemoryRecords]], EasyMock.capture(capturedArgument), EasyMock.anyObject().asInstanceOf[Option[ReentrantLock]], + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(new IAnswer[Unit] { override def answer = capturedArgument.getValue.apply( diff --git a/core/src/test/scala/unit/kafka/coordinator/group/GroupMetadataManagerTest.scala b/core/src/test/scala/unit/kafka/coordinator/group/GroupMetadataManagerTest.scala index 231382e515a6b..d2d3b12ba9381 100644 --- a/core/src/test/scala/unit/kafka/coordinator/group/GroupMetadataManagerTest.scala +++ b/core/src/test/scala/unit/kafka/coordinator/group/GroupMetadataManagerTest.scala @@ -21,15 +21,15 @@ import java.lang.management.ManagementFactory import java.nio.ByteBuffer import java.util.concurrent.locks.ReentrantLock import java.util.{Collections, Optional} - import com.yammer.metrics.core.Gauge + import javax.management.ObjectName import kafka.api._ import kafka.cluster.Partition import kafka.common.OffsetAndMetadata import kafka.log.{AppendOrigin, Log, LogAppendInfo} import kafka.metrics.KafkaYammerMetrics -import kafka.server.{FetchDataInfo, FetchLogEnd, HostedPartition, KafkaConfig, LogOffsetMetadata, ReplicaManager} +import kafka.server.{FetchDataInfo, FetchLogEnd, HostedPartition, KafkaConfig, LogOffsetMetadata, ReplicaManager, RequestLocal} import kafka.utils.{KafkaScheduler, MockTime, TestUtils} import org.apache.kafka.clients.consumer.ConsumerPartitionAssignor import org.apache.kafka.clients.consumer.ConsumerPartitionAssignor.Subscription @@ -105,7 +105,7 @@ class GroupMetadataManagerTest { var expiredOffsets: Int = 0 var infoCount = 0 val gmm = new GroupMetadataManager(0, ApiVersion.latestVersion, offsetConfig, replicaManager, time, metrics) { - override def cleanupGroupMetadata(groups: Iterable[GroupMetadata], + override def cleanupGroupMetadata(groups: Iterable[GroupMetadata], requestLocal: RequestLocal, selector: GroupMetadata => Map[TopicPartition, OffsetAndMetadata]): Int = expiredOffsets override def info(msg: => String): Unit = infoCount += 1 @@ -128,6 +128,7 @@ class GroupMetadataManagerTest { def testLoadOffsetsWithoutGroup(): Unit = { val groupMetadataTopicPartition = groupTopicPartition val startOffset = 15L + val groupEpoch = 2 val committedOffsets = Map( new TopicPartition("foo", 0) -> 23L, @@ -141,7 +142,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -158,6 +159,7 @@ class GroupMetadataManagerTest { val generation = 15 val protocolType = "consumer" val startOffset = 15L + val groupEpoch = 2 val committedOffsets = Map( new TopicPartition("foo", 0) -> 23L, new TopicPartition("foo", 1) -> 455L, @@ -173,7 +175,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -192,6 +194,7 @@ class GroupMetadataManagerTest { val groupMetadataTopicPartition = groupTopicPartition val producerId = 1000L val producerEpoch: Short = 2 + val groupEpoch = 2 val committedOffsets = Map( new TopicPartition("foo", 0) -> 23L, @@ -210,7 +213,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -226,6 +229,7 @@ class GroupMetadataManagerTest { val groupMetadataTopicPartition = groupTopicPartition val producerId = 1000L val producerEpoch: Short = 2 + val groupEpoch = 2 val abortedOffsets = Map( new TopicPartition("foo", 0) -> 23L, @@ -244,7 +248,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) // Since there are no committed offsets for the group, and there is no other group metadata, we don't expect the // group to be loaded. @@ -256,6 +260,7 @@ class GroupMetadataManagerTest { val groupMetadataTopicPartition = groupTopicPartition val producerId = 1000L val producerEpoch: Short = 2 + val groupEpoch = 2 val foo0 = new TopicPartition("foo", 0) val foo1 = new TopicPartition("foo", 1) @@ -276,7 +281,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) // The group should be loaded with pending offsets. val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) @@ -297,6 +302,7 @@ class GroupMetadataManagerTest { val groupMetadataTopicPartition = groupTopicPartition val producerId = 1000L val producerEpoch: Short = 2 + val groupEpoch = 2 val committedOffsets = Map( new TopicPartition("foo", 0) -> 23L, @@ -323,7 +329,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -342,6 +348,7 @@ class GroupMetadataManagerTest { val groupMetadataTopicPartition = groupTopicPartition val producerId = 1000L val producerEpoch: Short = 2 + val groupEpoch = 2 val committedOffsets = Map( new TopicPartition("foo", 0) -> 23L, @@ -378,7 +385,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -411,6 +418,7 @@ class GroupMetadataManagerTest { val firstProducerEpoch: Short = 2 val secondProducerId = 1001L val secondProducerEpoch: Short = 3 + val groupEpoch = 2 val committedOffsetsFirstProducer = Map( new TopicPartition("foo", 0) -> 23L, @@ -441,7 +449,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -465,6 +473,7 @@ class GroupMetadataManagerTest { val groupMetadataTopicPartition = groupTopicPartition val producerId = 1000L val producerEpoch: Short = 2 + val groupEpoch = 2 val transactionalOffsetCommits = Map( new TopicPartition("foo", 0) -> 23L @@ -487,7 +496,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) // The group should be loaded with pending offsets. val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) @@ -508,6 +517,7 @@ class GroupMetadataManagerTest { val groupMetadataTopicPartition = groupTopicPartition val producerId = 1000L val producerEpoch: Short = 2 + val groupEpoch = 2 val transactionalOffsetCommits = Map( new TopicPartition("foo", 0) -> 23L @@ -529,7 +539,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) // The group should be loaded with pending offsets. val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) @@ -596,6 +606,7 @@ class GroupMetadataManagerTest { def testLoadOffsetsWithTombstones(): Unit = { val groupMetadataTopicPartition = groupTopicPartition val startOffset = 15L + val groupEpoch = 2 val tombstonePartition = new TopicPartition("foo", 1) val committedOffsets = Map( @@ -613,7 +624,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -629,7 +640,10 @@ class GroupMetadataManagerTest { @Test def testLoadOffsetsAndGroup(): Unit = { - val groupMetadataTopicPartition = groupTopicPartition + loadOffsetsAndGroup(groupTopicPartition, 2) + } + + def loadOffsetsAndGroup(groupMetadataTopicPartition: TopicPartition, groupEpoch: Int): GroupMetadata = { val generation = 935 val protocolType = "consumer" val protocol = "range" @@ -650,7 +664,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -665,12 +679,92 @@ class GroupMetadataManagerTest { assertEquals(Some(offset), group.offset(topicPartition).map(_.offset)) assertTrue(group.offset(topicPartition).map(_.expireTimestamp).contains(None)) } + group + } + + @Test + def testLoadOffsetsAndGroupIgnored(): Unit = { + val groupEpoch = 2 + loadOffsetsAndGroup(groupTopicPartition, groupEpoch) + assertEquals(groupEpoch, groupMetadataManager.epochForPartitionId.get(groupTopicPartition.partition())) + + groupMetadataManager.removeGroupsAndOffsets(groupTopicPartition, Some(groupEpoch), _ => ()) + assertTrue(groupMetadataManager.getGroup(groupId).isEmpty, + "Removed group remained in cache") + assertEquals(groupEpoch, groupMetadataManager.epochForPartitionId.get(groupTopicPartition.partition())) + + groupMetadataManager.loadGroupsAndOffsets(groupTopicPartition, groupEpoch - 1, _ => (), 0L) + assertTrue(groupMetadataManager.getGroup(groupId).isEmpty, + "Removed group remained in cache") + assertEquals(groupEpoch, groupMetadataManager.epochForPartitionId.get(groupTopicPartition.partition())) + } + + @Test + def testUnloadOffsetsAndGroup(): Unit = { + val groupEpoch = 2 + loadOffsetsAndGroup(groupTopicPartition, groupEpoch) + + groupMetadataManager.removeGroupsAndOffsets(groupTopicPartition, Some(groupEpoch), _ => ()) + assertEquals(groupEpoch, groupMetadataManager.epochForPartitionId.get(groupTopicPartition.partition())) + assertTrue(groupMetadataManager.getGroup(groupId).isEmpty, + "Removed group remained in cache") + } + + @Test + def testUnloadOffsetsAndGroupIgnored(): Unit = { + val groupEpoch = 2 + val initiallyLoaded = loadOffsetsAndGroup(groupTopicPartition, groupEpoch) + + groupMetadataManager.removeGroupsAndOffsets(groupTopicPartition, Some(groupEpoch - 1), _ => ()) + assertEquals(groupEpoch, groupMetadataManager.epochForPartitionId.get(groupTopicPartition.partition())) + val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) + assertEquals(initiallyLoaded.groupId, group.groupId) + assertEquals(initiallyLoaded.currentState, group.currentState) + assertEquals(initiallyLoaded.leaderOrNull, group.leaderOrNull) + assertEquals(initiallyLoaded.generationId, group.generationId) + assertEquals(initiallyLoaded.protocolType, group.protocolType) + assertEquals(initiallyLoaded.protocolName.orNull, group.protocolName.orNull) + assertEquals(initiallyLoaded.allMembers, group.allMembers) + assertEquals(initiallyLoaded.allOffsets.size, group.allOffsets.size) + initiallyLoaded.allOffsets.foreach { case (topicPartition, offset) => + assertEquals(Some(offset), group.offset(topicPartition)) + assertTrue(group.offset(topicPartition).map(_.expireTimestamp).contains(None)) + } + } + + @Test + def testUnloadOffsetsAndGroupIgnoredAfterStopReplica(): Unit = { + val groupEpoch = 2 + val initiallyLoaded = loadOffsetsAndGroup(groupTopicPartition, groupEpoch) + + groupMetadataManager.removeGroupsAndOffsets(groupTopicPartition, None, _ => ()) + assertTrue(groupMetadataManager.getGroup(groupId).isEmpty, + "Removed group remained in cache") + assertEquals(groupEpoch, groupMetadataManager.epochForPartitionId.get(groupTopicPartition.partition()), + "Replica which was stopped still in epochForPartitionId") + + EasyMock.reset(replicaManager) + loadOffsetsAndGroup(groupTopicPartition, groupEpoch + 1) + val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) + assertEquals(initiallyLoaded.groupId, group.groupId) + assertEquals(initiallyLoaded.currentState, group.currentState) + assertEquals(initiallyLoaded.leaderOrNull, group.leaderOrNull) + assertEquals(initiallyLoaded.generationId, group.generationId) + assertEquals(initiallyLoaded.protocolType, group.protocolType) + assertEquals(initiallyLoaded.protocolName.orNull, group.protocolName.orNull) + assertEquals(initiallyLoaded.allMembers, group.allMembers) + assertEquals(initiallyLoaded.allOffsets.size, group.allOffsets.size) + initiallyLoaded.allOffsets.foreach { case (topicPartition, offset) => + assertEquals(Some(offset), group.offset(topicPartition)) + assertTrue(group.offset(topicPartition).map(_.expireTimestamp).contains(None)) + } } @Test def testLoadGroupWithTombstone(): Unit = { val groupMetadataTopicPartition = groupTopicPartition val startOffset = 15L + val groupEpoch = 2 val memberId = "98098230493" val groupMetadataRecord = buildStableGroupRecordWithMember(generation = 15, protocolType = "consumer", protocol = "range", memberId) @@ -682,7 +776,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) assertEquals(None, groupMetadataManager.getGroup(groupId)) } @@ -691,6 +785,7 @@ class GroupMetadataManagerTest { def testLoadGroupWithLargeGroupMetadataRecord(): Unit = { val groupMetadataTopicPartition = groupTopicPartition val startOffset = 15L + val groupEpoch = 2 val committedOffsets = Map( new TopicPartition("foo", 0) -> 23L, new TopicPartition("foo", 1) -> 455L, @@ -711,7 +806,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) committedOffsets.foreach { case (topicPartition, offset) => @@ -726,6 +821,7 @@ class GroupMetadataManagerTest { // is accidentally corrupted. val startOffset = 0L val endOffset = 10L + val groupEpoch = 2 val logMock: Log = EasyMock.mock(classOf[Log]) EasyMock.expect(replicaManager.getLog(groupTopicPartition)).andStubReturn(Some(logMock)) @@ -735,7 +831,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupTopicPartition, groupEpoch, _ => (), 0L) EasyMock.verify(logMock) EasyMock.verify(replicaManager) @@ -754,6 +850,7 @@ class GroupMetadataManagerTest { val protocolType = "consumer" val protocol = "range" val startOffset = 15L + val groupEpoch = 2 val committedOffsets = Map( new TopicPartition("foo", 0) -> 23L, @@ -771,7 +868,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -788,6 +885,7 @@ class GroupMetadataManagerTest { val protocolType = "consumer" val protocol = "range" val startOffset = 15L + val groupEpoch = 2 val tp0 = new TopicPartition("foo", 0) val tp1 = new TopicPartition("foo", 1) val tp2 = new TopicPartition("bar", 0) @@ -814,7 +912,7 @@ class GroupMetadataManagerTest { EasyMock.replay(logMock, replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -1418,8 +1516,8 @@ class GroupMetadataManagerTest { EasyMock.reset(partition) EasyMock.expect(partition.appendRecordsToLeader(EasyMock.anyObject(classOf[MemoryRecords]), - origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt())) - .andReturn(LogAppendInfo.UnknownLogAppendInfo) + origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt(), + EasyMock.anyObject())).andReturn(LogAppendInfo.UnknownLogAppendInfo) EasyMock.replay(partition) groupMetadataManager.cleanupGroupMetadata() @@ -1453,8 +1551,8 @@ class GroupMetadataManagerTest { EasyMock.expect(replicaManager.getMagic(EasyMock.anyObject())).andStubReturn(Some(RecordBatch.CURRENT_MAGIC_VALUE)) mockGetPartition() EasyMock.expect(partition.appendRecordsToLeader(EasyMock.capture(recordsCapture), - origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt())) - .andReturn(LogAppendInfo.UnknownLogAppendInfo) + origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt(), + EasyMock.anyObject())).andReturn(LogAppendInfo.UnknownLogAppendInfo) EasyMock.replay(replicaManager, partition) groupMetadataManager.cleanupGroupMetadata() @@ -1501,8 +1599,8 @@ class GroupMetadataManagerTest { EasyMock.expect(replicaManager.getMagic(EasyMock.anyObject())).andStubReturn(Some(RecordBatch.CURRENT_MAGIC_VALUE)) mockGetPartition() EasyMock.expect(partition.appendRecordsToLeader(EasyMock.capture(recordsCapture), - origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt())) - .andReturn(LogAppendInfo.UnknownLogAppendInfo) + origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt(), + EasyMock.anyObject())).andReturn(LogAppendInfo.UnknownLogAppendInfo) EasyMock.replay(replicaManager, partition) groupMetadataManager.cleanupGroupMetadata() @@ -1576,8 +1674,8 @@ class GroupMetadataManagerTest { val recordsCapture: Capture[MemoryRecords] = EasyMock.newCapture() EasyMock.expect(partition.appendRecordsToLeader(EasyMock.capture(recordsCapture), - origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt())) - .andReturn(LogAppendInfo.UnknownLogAppendInfo) + origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt(), + EasyMock.anyObject())).andReturn(LogAppendInfo.UnknownLogAppendInfo) EasyMock.replay(partition) groupMetadataManager.cleanupGroupMetadata() @@ -1677,8 +1775,8 @@ class GroupMetadataManagerTest { // expect the offset tombstone EasyMock.reset(partition) EasyMock.expect(partition.appendRecordsToLeader(EasyMock.anyObject(classOf[MemoryRecords]), - origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt())) - .andReturn(LogAppendInfo.UnknownLogAppendInfo) + origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt(), + EasyMock.anyObject())).andReturn(LogAppendInfo.UnknownLogAppendInfo) EasyMock.replay(partition) groupMetadataManager.cleanupGroupMetadata() @@ -1701,8 +1799,8 @@ class GroupMetadataManagerTest { // expect the offset tombstone EasyMock.reset(partition) EasyMock.expect(partition.appendRecordsToLeader(EasyMock.anyObject(classOf[MemoryRecords]), - origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt())) - .andReturn(LogAppendInfo.UnknownLogAppendInfo) + origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt(), + EasyMock.anyObject())).andReturn(LogAppendInfo.UnknownLogAppendInfo) EasyMock.replay(partition) groupMetadataManager.cleanupGroupMetadata() @@ -1744,8 +1842,8 @@ class GroupMetadataManagerTest { // expect the offset tombstone EasyMock.reset(partition) EasyMock.expect(partition.appendRecordsToLeader(EasyMock.anyObject(classOf[MemoryRecords]), - origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt())) - .andReturn(LogAppendInfo.UnknownLogAppendInfo) + origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt(), + EasyMock.anyObject())).andReturn(LogAppendInfo.UnknownLogAppendInfo) EasyMock.replay(partition) groupMetadataManager.cleanupGroupMetadata() @@ -1822,8 +1920,8 @@ class GroupMetadataManagerTest { // expect the offset tombstone EasyMock.reset(partition) EasyMock.expect(partition.appendRecordsToLeader(EasyMock.anyObject(classOf[MemoryRecords]), - origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt())) - .andReturn(LogAppendInfo.UnknownLogAppendInfo) + origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt(), + EasyMock.anyObject())).andReturn(LogAppendInfo.UnknownLogAppendInfo) EasyMock.replay(partition) groupMetadataManager.cleanupGroupMetadata() @@ -1948,8 +2046,8 @@ class GroupMetadataManagerTest { // expect the offset tombstone EasyMock.expect(partition.appendRecordsToLeader(EasyMock.anyObject(classOf[MemoryRecords]), - origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt())) - .andReturn(LogAppendInfo.UnknownLogAppendInfo) + origin = EasyMock.eq(AppendOrigin.Coordinator), requiredAcks = EasyMock.anyInt(), + EasyMock.anyObject())).andReturn(LogAppendInfo.UnknownLogAppendInfo) EasyMock.expectLastCall().times(1) EasyMock.replay(partition) @@ -1982,6 +2080,7 @@ class GroupMetadataManagerTest { val protocolType = "consumer" val protocol = "range" val startOffset = 15L + val groupEpoch = 2 val committedOffsets = Map( new TopicPartition("foo", 0) -> 23L, new TopicPartition("foo", 1) -> 455L, @@ -1999,7 +2098,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -2023,6 +2122,7 @@ class GroupMetadataManagerTest { val protocolType = "consumer" val protocol = "range" val startOffset = 15L + val groupEpoch = 2 val committedOffsets = Map( new TopicPartition("foo", 0) -> 23L, new TopicPartition("foo", 1) -> 455L, @@ -2039,7 +2139,7 @@ class GroupMetadataManagerTest { EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) assertEquals(groupId, group.groupId) @@ -2153,6 +2253,7 @@ class GroupMetadataManagerTest { val groupMetadataTopicPartition = groupTopicPartition val startOffset = 15L val generation = 15 + val groupEpoch = 2 val committedOffsets = Map( new TopicPartition("foo", 0) -> 23L, @@ -2190,7 +2291,7 @@ class GroupMetadataManagerTest { EasyMock.replay(logMock) EasyMock.replay(replicaManager) - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), 0L) + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), 0L) // Empty control batch should not have caused the load to fail val group = groupMetadataManager.getGroup(groupId).getOrElse(throw new AssertionError("Group was not loaded into the cache")) @@ -2270,6 +2371,7 @@ class GroupMetadataManagerTest { EasyMock.anyObject().asInstanceOf[Map[TopicPartition, MemoryRecords]], EasyMock.capture(capturedArgument), EasyMock.anyObject().asInstanceOf[Option[ReentrantLock]], + EasyMock.anyObject(), EasyMock.anyObject()) ) EasyMock.expect(replicaManager.getMagic(EasyMock.anyObject())).andStubReturn(Some(RecordBatch.CURRENT_MAGIC_VALUE)) @@ -2286,6 +2388,7 @@ class GroupMetadataManagerTest { EasyMock.capture(capturedRecords), EasyMock.capture(capturedCallback), EasyMock.anyObject().asInstanceOf[Option[ReentrantLock]], + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(new IAnswer[Unit] { override def answer = capturedCallback.getValue.apply( @@ -2453,7 +2556,8 @@ class GroupMetadataManagerTest { // When passed a specific start offset, assert that the measured values are in excess of that. val now = time.milliseconds() val diff = 1000 - groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, _ => (), now - diff) + val groupEpoch = 2 + groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, groupEpoch, _ => (), now - diff) assertTrue(partitionLoadTime("partition-load-time-max") >= diff) assertTrue(partitionLoadTime("partition-load-time-avg") >= diff) } diff --git a/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionCoordinatorConcurrencyTest.scala b/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionCoordinatorConcurrencyTest.scala index e1786d0ee21ff..e02c2fe3c320b 100644 --- a/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionCoordinatorConcurrencyTest.scala +++ b/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionCoordinatorConcurrencyTest.scala @@ -18,12 +18,11 @@ package kafka.coordinator.transaction import java.nio.ByteBuffer import java.util.concurrent.atomic.AtomicBoolean - import kafka.coordinator.AbstractCoordinatorConcurrencyTest import kafka.coordinator.AbstractCoordinatorConcurrencyTest._ import kafka.coordinator.transaction.TransactionCoordinatorConcurrencyTest._ import kafka.log.Log -import kafka.server.{FetchDataInfo, FetchLogEnd, KafkaConfig, LogOffsetMetadata, MetadataCache} +import kafka.server.{FetchDataInfo, FetchLogEnd, KafkaConfig, LogOffsetMetadata, MetadataCache, RequestLocal} import kafka.utils.{Pool, TestUtils} import org.apache.kafka.clients.{ClientResponse, NetworkClient} import org.apache.kafka.common.internals.Topic.TRANSACTION_STATE_TOPIC_NAME @@ -509,7 +508,8 @@ class TransactionCoordinatorConcurrencyTest extends AbstractCoordinatorConcurren class InitProducerIdOperation(val producerIdAndEpoch: Option[ProducerIdAndEpoch] = None) extends TxnOperation[InitProducerIdResult] { override def run(txn: Transaction): Unit = { - transactionCoordinator.handleInitProducerId(txn.transactionalId, 60000, producerIdAndEpoch, resultCallback) + transactionCoordinator.handleInitProducerId(txn.transactionalId, 60000, producerIdAndEpoch, resultCallback, + RequestLocal.withThreadConfinedCaching) replicaManager.tryCompleteActions() } override def awaitAndVerify(txn: Transaction): Unit = { @@ -526,7 +526,8 @@ class TransactionCoordinatorConcurrencyTest extends AbstractCoordinatorConcurren txnMetadata.producerId, txnMetadata.producerEpoch, partitions, - resultCallback) + resultCallback, + RequestLocal.withThreadConfinedCaching) replicaManager.tryCompleteActions() } } @@ -544,7 +545,8 @@ class TransactionCoordinatorConcurrencyTest extends AbstractCoordinatorConcurren txnMetadata.producerId, txnMetadata.producerEpoch, transactionResult(txn), - resultCallback) + resultCallback, + RequestLocal.withThreadConfinedCaching) } } override def awaitAndVerify(txn: Transaction): Unit = { diff --git a/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionCoordinatorTest.scala b/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionCoordinatorTest.scala index f6b5e54dfe19a..38e8e711975ef 100644 --- a/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionCoordinatorTest.scala +++ b/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionCoordinatorTest.scala @@ -118,6 +118,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.anyObject().asInstanceOf[TxnTransitMetadata], EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => capturedErrorsCallback.getValue.apply(Errors.NONE)).anyTimes() EasyMock.replay(pidGenerator, transactionManager) @@ -145,6 +146,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.anyObject().asInstanceOf[TxnTransitMetadata], EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => capturedErrorsCallback.getValue.apply(Errors.NONE)).anyTimes() EasyMock.replay(pidGenerator, transactionManager) @@ -169,6 +171,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.anyObject().asInstanceOf[TxnTransitMetadata], EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject() )).andAnswer(() => capturedErrorsCallback.getValue.apply(Errors.NONE)) @@ -314,6 +317,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.anyObject().asInstanceOf[TxnTransitMetadata], EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject() )) @@ -572,6 +576,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.eq(originalMetadata.prepareAbortOrCommit(PrepareAbort, time.milliseconds())), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => capturedErrorsCallback.getValue.apply(Errors.NONE)) @@ -641,6 +646,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.eq(txnTransitMetadata), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => { capturedErrorsCallback.getValue.apply(Errors.NOT_ENOUGH_REPLICAS) @@ -652,6 +658,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.eq(txnTransitMetadata), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => { capturedErrorsCallback.getValue.apply(Errors.NONE) @@ -724,6 +731,7 @@ class TransactionCoordinatorTest { txnStartTimestamp = time.milliseconds(), txnLastUpdateTimestamp = time.milliseconds())), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => capturedErrorsCallback.getValue.apply(Errors.NONE)) @@ -790,6 +798,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.anyObject().asInstanceOf[TxnTransitMetadata], EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => { capturedErrorsCallback.getValue.apply(Errors.NONE) @@ -827,6 +836,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.capture(capturedTxnTransitMetadata), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => { capturedErrorsCallback.getValue.apply(Errors.NONE) @@ -867,6 +877,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.capture(capturedTxnTransitMetadata), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => { capturedErrorsCallback.getValue.apply(Errors.NONE) @@ -910,6 +921,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.capture(capturedTxnTransitMetadata), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => { capturedErrorsCallback.getValue.apply(Errors.NONE) @@ -963,6 +975,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.eq(expectedTransition), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => {}).once() @@ -1046,6 +1059,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.eq(expectedTransition), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => capturedErrorsCallback.getValue.apply(Errors.NOT_ENOUGH_REPLICAS)).once() @@ -1179,6 +1193,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.capture(capturedNewMetadata), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject() )).andAnswer(() => { metadata.completeTransitionTo(capturedNewMetadata.getValue) @@ -1213,6 +1228,7 @@ class TransactionCoordinatorTest { EasyMock.eq(coordinatorEpoch), EasyMock.eq(transition), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => { if (runCallback) diff --git a/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionMarkerChannelManagerTest.scala b/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionMarkerChannelManagerTest.scala index 8aa07c6c627ce..0a0ec511535d0 100644 --- a/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionMarkerChannelManagerTest.scala +++ b/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionMarkerChannelManagerTest.scala @@ -107,6 +107,7 @@ class TransactionMarkerChannelManagerTest { EasyMock.eq(coordinatorEpoch), EasyMock.eq(expectedTransition), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject())) .andAnswer(() => { txnMetadata2.completeTransitionTo(expectedTransition) @@ -345,6 +346,7 @@ class TransactionMarkerChannelManagerTest { EasyMock.eq(coordinatorEpoch), EasyMock.eq(txnTransitionMetadata2), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject())) .andAnswer(() => { txnMetadata2.completeTransitionTo(txnTransitionMetadata2) @@ -392,6 +394,7 @@ class TransactionMarkerChannelManagerTest { EasyMock.eq(coordinatorEpoch), EasyMock.eq(txnTransitionMetadata2), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject())) .andAnswer(() => { txnMetadata2.pendingState = None @@ -439,6 +442,7 @@ class TransactionMarkerChannelManagerTest { EasyMock.eq(coordinatorEpoch), EasyMock.eq(txnTransitionMetadata2), EasyMock.capture(capturedErrorsCallback), + EasyMock.anyObject(), EasyMock.anyObject())) .andAnswer(() => capturedErrorsCallback.getValue.apply(Errors.COORDINATOR_NOT_AVAILABLE)) .andAnswer(() => { diff --git a/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionStateManagerTest.scala b/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionStateManagerTest.scala index df576931525d6..410d6e2a26d06 100644 --- a/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionStateManagerTest.scala +++ b/core/src/test/scala/unit/kafka/coordinator/transaction/TransactionStateManagerTest.scala @@ -22,7 +22,7 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.locks.ReentrantLock import javax.management.ObjectName import kafka.log.{AppendOrigin, Log} -import kafka.server.{FetchDataInfo, FetchLogEnd, LogOffsetMetadata, ReplicaManager} +import kafka.server.{FetchDataInfo, FetchLogEnd, LogOffsetMetadata, ReplicaManager, RequestLocal} import kafka.utils.{MockScheduler, Pool, TestUtils} import kafka.zk.KafkaZkClient import org.apache.kafka.common.TopicPartition @@ -319,7 +319,7 @@ class TransactionStateManagerTest { new TopicPartition("topic1", 1)), time.milliseconds()) // append the new metadata into log - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch, newMetadata, assertCallback) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch, newMetadata, assertCallback, requestLocal = RequestLocal.withThreadConfinedCaching) assertEquals(Right(Some(CoordinatorEpochAndTxnMetadata(coordinatorEpoch, txnMetadata1))), transactionManager.getTransactionState(transactionalId1)) assertTrue(txnMetadata1.pendingState.isEmpty) @@ -334,25 +334,26 @@ class TransactionStateManagerTest { var failedMetadata = txnMetadata1.prepareAddPartitions(Set[TopicPartition](new TopicPartition("topic2", 0)), time.milliseconds()) prepareForTxnMessageAppend(Errors.UNKNOWN_TOPIC_OR_PARTITION) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + val requestLocal = RequestLocal.withThreadConfinedCaching + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = requestLocal) assertEquals(Right(Some(CoordinatorEpochAndTxnMetadata(coordinatorEpoch, txnMetadata1))), transactionManager.getTransactionState(transactionalId1)) assertTrue(txnMetadata1.pendingState.isEmpty) failedMetadata = txnMetadata1.prepareAddPartitions(Set[TopicPartition](new TopicPartition("topic2", 0)), time.milliseconds()) prepareForTxnMessageAppend(Errors.NOT_ENOUGH_REPLICAS) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = requestLocal) assertEquals(Right(Some(CoordinatorEpochAndTxnMetadata(coordinatorEpoch, txnMetadata1))), transactionManager.getTransactionState(transactionalId1)) assertTrue(txnMetadata1.pendingState.isEmpty) failedMetadata = txnMetadata1.prepareAddPartitions(Set[TopicPartition](new TopicPartition("topic2", 0)), time.milliseconds()) prepareForTxnMessageAppend(Errors.NOT_ENOUGH_REPLICAS_AFTER_APPEND) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = requestLocal) assertEquals(Right(Some(CoordinatorEpochAndTxnMetadata(coordinatorEpoch, txnMetadata1))), transactionManager.getTransactionState(transactionalId1)) assertTrue(txnMetadata1.pendingState.isEmpty) failedMetadata = txnMetadata1.prepareAddPartitions(Set[TopicPartition](new TopicPartition("topic2", 0)), time.milliseconds()) prepareForTxnMessageAppend(Errors.REQUEST_TIMED_OUT) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = requestLocal) assertEquals(Right(Some(CoordinatorEpochAndTxnMetadata(coordinatorEpoch, txnMetadata1))), transactionManager.getTransactionState(transactionalId1)) assertTrue(txnMetadata1.pendingState.isEmpty) } @@ -366,25 +367,26 @@ class TransactionStateManagerTest { var failedMetadata = txnMetadata1.prepareAddPartitions(Set[TopicPartition](new TopicPartition("topic2", 0)), time.milliseconds()) prepareForTxnMessageAppend(Errors.NOT_LEADER_OR_FOLLOWER) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + val requestLocal = RequestLocal.withThreadConfinedCaching + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = requestLocal) assertEquals(Right(Some(CoordinatorEpochAndTxnMetadata(coordinatorEpoch, txnMetadata1))), transactionManager.getTransactionState(transactionalId1)) assertTrue(txnMetadata1.pendingState.isEmpty) failedMetadata = txnMetadata1.prepareAddPartitions(Set[TopicPartition](new TopicPartition("topic2", 0)), time.milliseconds()) prepareForTxnMessageAppend(Errors.NONE) transactionManager.removeTransactionsForTxnTopicPartition(partitionId, coordinatorEpoch) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = requestLocal) prepareForTxnMessageAppend(Errors.NONE) transactionManager.removeTransactionsForTxnTopicPartition(partitionId, coordinatorEpoch) transactionManager.addLoadedTransactionsToCache(partitionId, coordinatorEpoch + 1, new Pool[String, TransactionMetadata]()) transactionManager.putTransactionStateIfNotExists(txnMetadata1) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = requestLocal) prepareForTxnMessageAppend(Errors.NONE) transactionManager.removeTransactionsForTxnTopicPartition(partitionId, coordinatorEpoch) transactionManager.addLoadedTransactionsToCache(partitionId, coordinatorEpoch, new Pool[String, TransactionMetadata]()) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = requestLocal) } @Test @@ -398,7 +400,7 @@ class TransactionStateManagerTest { prepareForTxnMessageAppend(Errors.NONE) transactionManager.removeTransactionsForTxnTopicPartition(partitionId, coordinatorEpoch) transactionManager.addLoadingPartition(partitionId, coordinatorEpoch + 1) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = RequestLocal.withThreadConfinedCaching) } @Test @@ -410,13 +412,14 @@ class TransactionStateManagerTest { var failedMetadata = txnMetadata1.prepareAddPartitions(Set[TopicPartition](new TopicPartition("topic2", 0)), time.milliseconds()) prepareForTxnMessageAppend(Errors.MESSAGE_TOO_LARGE) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + val requestLocal = RequestLocal.withThreadConfinedCaching + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = requestLocal) assertEquals(Right(Some(CoordinatorEpochAndTxnMetadata(coordinatorEpoch, txnMetadata1))), transactionManager.getTransactionState(transactionalId1)) assertTrue(txnMetadata1.pendingState.isEmpty) failedMetadata = txnMetadata1.prepareAddPartitions(Set[TopicPartition](new TopicPartition("topic2", 0)), time.milliseconds()) prepareForTxnMessageAppend(Errors.RECORD_LIST_TOO_LARGE) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, requestLocal = requestLocal) assertEquals(Right(Some(CoordinatorEpochAndTxnMetadata(coordinatorEpoch, txnMetadata1))), transactionManager.getTransactionState(transactionalId1)) assertTrue(txnMetadata1.pendingState.isEmpty) } @@ -430,7 +433,7 @@ class TransactionStateManagerTest { val failedMetadata = txnMetadata1.prepareAddPartitions(Set[TopicPartition](new TopicPartition("topic2", 0)), time.milliseconds()) prepareForTxnMessageAppend(Errors.UNKNOWN_TOPIC_OR_PARTITION) - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, _ => true) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, failedMetadata, assertCallback, _ => true, RequestLocal.withThreadConfinedCaching) assertEquals(Right(Some(CoordinatorEpochAndTxnMetadata(coordinatorEpoch, txnMetadata1))), transactionManager.getTransactionState(transactionalId1)) assertEquals(Some(Ongoing), txnMetadata1.pendingState) } @@ -452,7 +455,7 @@ class TransactionStateManagerTest { txnMetadata1.producerEpoch = (txnMetadata1.producerEpoch + 1).toShort // append the new metadata into log - transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, newMetadata, assertCallback) + transactionManager.appendTransactionToLog(transactionalId1, coordinatorEpoch = 10, newMetadata, assertCallback, requestLocal = RequestLocal.withThreadConfinedCaching) } @Test @@ -472,7 +475,7 @@ class TransactionStateManagerTest { // append the new metadata into log assertThrows(classOf[IllegalStateException], () => transactionManager.appendTransactionToLog(transactionalId1, - coordinatorEpoch = 10, newMetadata, assertCallback)) + coordinatorEpoch = 10, newMetadata, assertCallback, requestLocal = RequestLocal.withThreadConfinedCaching)) } @Test @@ -719,6 +722,7 @@ class TransactionStateManagerTest { EasyMock.eq(recordsByPartition), EasyMock.capture(capturedArgument), EasyMock.anyObject().asInstanceOf[Option[ReentrantLock]], + EasyMock.anyObject(), EasyMock.anyObject() )).andAnswer(() => capturedArgument.getValue.apply( Map(partition -> new PartitionResponse(error, 0L, RecordBatch.NO_TIMESTAMP, 0L))) @@ -824,6 +828,7 @@ class TransactionStateManagerTest { EasyMock.anyObject().asInstanceOf[Map[TopicPartition, MemoryRecords]], EasyMock.capture(capturedArgument), EasyMock.anyObject().asInstanceOf[Option[ReentrantLock]], + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => capturedArgument.getValue.apply( Map(new TopicPartition(TRANSACTION_STATE_TOPIC_NAME, partitionId) -> diff --git a/core/src/test/scala/unit/kafka/log/LogCleanerManagerTest.scala b/core/src/test/scala/unit/kafka/log/LogCleanerManagerTest.scala index 7620348264711..19de8ea2703bb 100644 --- a/core/src/test/scala/unit/kafka/log/LogCleanerManagerTest.scala +++ b/core/src/test/scala/unit/kafka/log/LogCleanerManagerTest.scala @@ -121,7 +121,7 @@ class LogCleanerManagerTest extends Logging { extends Log(dir, config, segments, offsets.logStartOffset, offsets.recoveryPoint, offsets.nextOffsetMetadata, time.scheduler, new BrokerTopicStats, time, LogManager.ProducerIdExpirationCheckIntervalMs, topicPartition, leaderEpochCache, - producerStateManager, logDirFailureChannel, topicId = None, keepPartitionMetadataFile = true) { + producerStateManager, logDirFailureChannel, _topicId = None, keepPartitionMetadataFile = true) { // Throw an error in getFirstBatchTimestampForSegments since it is called in grabFilthiestLog() override def getFirstBatchTimestampForSegments(segments: Iterable[LogSegment]): Iterable[Long] = throw new IllegalStateException("Error!") diff --git a/core/src/test/scala/unit/kafka/log/LogCleanerTest.scala b/core/src/test/scala/unit/kafka/log/LogCleanerTest.scala index 0890b038c853b..5c91041b9f942 100755 --- a/core/src/test/scala/unit/kafka/log/LogCleanerTest.scala +++ b/core/src/test/scala/unit/kafka/log/LogCleanerTest.scala @@ -136,7 +136,7 @@ class LogCleanerTest { leaderEpochCache = leaderEpochCache, producerStateManager = producerStateManager, logDirFailureChannel = logDirFailureChannel, - topicId = None, + _topicId = None, keepPartitionMetadataFile = true) { override def replaceSegments(newSegments: Seq[LogSegment], oldSegments: Seq[LogSegment], isRecoveredSwapFile: Boolean = false): Unit = { deleteStartLatch.countDown() diff --git a/core/src/test/scala/unit/kafka/log/LogLoaderTest.scala b/core/src/test/scala/unit/kafka/log/LogLoaderTest.scala index 4826784836303..9a7b627492472 100644 --- a/core/src/test/scala/unit/kafka/log/LogLoaderTest.scala +++ b/core/src/test/scala/unit/kafka/log/LogLoaderTest.scala @@ -284,7 +284,7 @@ class LogLoaderTest { new Log(logDir, logConfig, interceptedLogSegments, offsets.logStartOffset, offsets.recoveryPoint, offsets.nextOffsetMetadata, mockTime.scheduler, brokerTopicStats, mockTime, LogManager.ProducerIdExpirationCheckIntervalMs, topicPartition, leaderEpochCache, - producerStateManager, logDirFailureChannel, topicId = None, keepPartitionMetadataFile = true) + producerStateManager, logDirFailureChannel, _topicId = None, keepPartitionMetadataFile = true) } // Retain snapshots for the last 2 segments @@ -366,7 +366,7 @@ class LogLoaderTest { leaderEpochCache = leaderEpochCache, producerStateManager = stateManager, logDirFailureChannel = logDirFailureChannel, - topicId = None, + _topicId = None, keepPartitionMetadataFile = true) EasyMock.verify(stateManager) @@ -500,7 +500,7 @@ class LogLoaderTest { leaderEpochCache = leaderEpochCache, producerStateManager = stateManager, logDirFailureChannel = logDirFailureChannel, - topicId = None, + _topicId = None, keepPartitionMetadataFile = true) EasyMock.verify(stateManager) @@ -561,7 +561,7 @@ class LogLoaderTest { leaderEpochCache = leaderEpochCache, producerStateManager = stateManager, logDirFailureChannel = logDirFailureChannel, - topicId = None, + _topicId = None, keepPartitionMetadataFile = true) EasyMock.verify(stateManager) @@ -624,7 +624,7 @@ class LogLoaderTest { leaderEpochCache = leaderEpochCache, producerStateManager = stateManager, logDirFailureChannel = logDirFailureChannel, - topicId = None, + _topicId = None, keepPartitionMetadataFile = true) EasyMock.verify(stateManager) diff --git a/core/src/test/scala/unit/kafka/log/LogSegmentsTest.scala b/core/src/test/scala/unit/kafka/log/LogSegmentsTest.scala index b929b9c4b8a52..9d0765aed687e 100644 --- a/core/src/test/scala/unit/kafka/log/LogSegmentsTest.scala +++ b/core/src/test/scala/unit/kafka/log/LogSegmentsTest.scala @@ -46,7 +46,7 @@ class LogSegmentsTest { Utils.delete(logDir) } - private def assertEntry(segment: LogSegment, tested: java.util.Map.Entry[java.lang.Long, LogSegment]): Unit = { + private def assertEntry(segment: LogSegment, tested: java.util.Map.Entry[Long, LogSegment]): Unit = { assertEquals(segment.baseOffset, tested.getKey()) assertEquals(segment, tested.getValue()) } @@ -158,17 +158,13 @@ class LogSegmentsTest { List(seg1, seg2, seg3, seg4).foreach(segments.add) - // Test floorSegment, floorEntry + // Test floorSegment assertEquals(Some(seg1), segments.floorSegment(2)) - assertEntry(seg1, segments.floorEntry(2).get) assertEquals(Some(seg2), segments.floorSegment(3)) - assertEntry(seg2, segments.floorEntry(3).get) - // Test lowerSegment, lowerEntry + // Test lowerSegment assertEquals(Some(seg1), segments.lowerSegment(3)) - assertEntry(seg1, segments.lowerEntry(3).get) assertEquals(Some(seg2), segments.lowerSegment(4)) - assertEntry(seg2, segments.lowerEntry(4).get) // Test higherSegment, higherEntry assertEquals(Some(seg3), segments.higherSegment(4)) @@ -178,4 +174,53 @@ class LogSegmentsTest { segments.close() } + + @Test + def testHigherSegments(): Unit = { + val segments = new LogSegments(topicPartition) + + val seg1 = createSegment(1) + val seg2 = createSegment(3) + val seg3 = createSegment(5) + val seg4 = createSegment(7) + val seg5 = createSegment(9) + + List(seg1, seg2, seg3, seg4, seg5).foreach(segments.add) + + // higherSegments(0) should return all segments in order + { + val iterator = segments.higherSegments(0).iterator + List(seg1, seg2, seg3, seg4, seg5).foreach { + segment => + assertTrue(iterator.hasNext) + assertEquals(segment, iterator.next()) + } + assertFalse(iterator.hasNext) + } + + // higherSegments(1) should return all segments in order except seg1 + { + val iterator = segments.higherSegments(1).iterator + List(seg2, seg3, seg4, seg5).foreach { + segment => + assertTrue(iterator.hasNext) + assertEquals(segment, iterator.next()) + } + assertFalse(iterator.hasNext) + } + + // higherSegments(8) should return only seg5 + { + val iterator = segments.higherSegments(8).iterator + assertTrue(iterator.hasNext) + assertEquals(seg5, iterator.next()) + assertFalse(iterator.hasNext) + } + + // higherSegments(9) should return no segments + { + val iterator = segments.higherSegments(9).iterator + assertFalse(iterator.hasNext) + } + } } diff --git a/core/src/test/scala/unit/kafka/log/LogTest.scala b/core/src/test/scala/unit/kafka/log/LogTest.scala index 30541165a8289..4cc88d0f5873c 100755 --- a/core/src/test/scala/unit/kafka/log/LogTest.scala +++ b/core/src/test/scala/unit/kafka/log/LogTest.scala @@ -2319,9 +2319,8 @@ class LogTest { val log = createLog(logDir, logConfig) // Write a topic ID to the partition metadata file to ensure it is transferred correctly. - val id = Uuid.randomUuid() - log.topicId = Some(id) - log.partitionMetadataFile.write(id) + val topicId = Uuid.randomUuid() + log.assignTopicId(topicId) log.appendAsLeader(TestUtils.records(List(new SimpleRecord("foo".getBytes()))), leaderEpoch = 5) assertEquals(Some(5), log.latestEpoch) @@ -2336,8 +2335,8 @@ class LogTest { // Check the topic ID remains in memory and was copied correctly. assertTrue(log.topicId.isDefined) - assertEquals(id, log.topicId.get) - assertEquals(id, log.partitionMetadataFile.read().topicId) + assertEquals(topicId, log.topicId.get) + assertEquals(topicId, log.partitionMetadataFile.read().topicId) } @Test diff --git a/core/src/test/scala/unit/kafka/log/LogValidatorTest.scala b/core/src/test/scala/unit/kafka/log/LogValidatorTest.scala index b0d4e3dc7efa3..af585bfd49605 100644 --- a/core/src/test/scala/unit/kafka/log/LogValidatorTest.scala +++ b/core/src/test/scala/unit/kafka/log/LogValidatorTest.scala @@ -18,13 +18,12 @@ package kafka.log import java.nio.ByteBuffer import java.util.concurrent.TimeUnit - import kafka.api.{ApiVersion, KAFKA_2_0_IV1, KAFKA_2_3_IV1} import kafka.common.{LongRef, RecordValidationException} import kafka.log.LogValidator.ValidationAndOffsetAssignResult import kafka.message._ import kafka.metrics.KafkaYammerMetrics -import kafka.server.BrokerTopicStats +import kafka.server.{BrokerTopicStats, RequestLocal} import kafka.utils.TestUtils.meterCount import org.apache.kafka.common.errors.{InvalidTimestampException, UnsupportedCompressionTypeException, UnsupportedForMessageFormatException} import org.apache.kafka.common.record._ @@ -128,8 +127,8 @@ class LogValidatorTest { RecordBatch.NO_PRODUCER_EPOCH, origin = AppendOrigin.Client, KAFKA_2_3_IV1, - brokerTopicStats - ) + brokerTopicStats, + RequestLocal.withThreadConfinedCaching) } @Test @@ -160,7 +159,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) val validatedRecords = validatedResults.validatedRecords assertEquals(records.records.asScala.size, validatedRecords.records.asScala.size, "message set size should not change") validatedRecords.batches.forEach(batch => validateLogAppendTime(now, 1234L, batch)) @@ -199,7 +199,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) val validatedRecords = validatedResults.validatedRecords assertEquals(records.records.asScala.size, validatedRecords.records.asScala.size, @@ -247,7 +248,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) val validatedRecords = validatedResults.validatedRecords assertEquals(records.records.asScala.size, validatedRecords.records.asScala.size, @@ -309,7 +311,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) } @Test @@ -353,7 +356,8 @@ class LogValidatorTest { partitionLeaderEpoch = partitionLeaderEpoch, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) val validatedRecords = validatingResults.validatedRecords var i = 0 @@ -425,7 +429,8 @@ class LogValidatorTest { partitionLeaderEpoch = partitionLeaderEpoch, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) val validatedRecords = validatingResults.validatedRecords var i = 0 @@ -481,7 +486,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) val validatedRecords = validatedResults.validatedRecords for (batch <- validatedRecords.batches.asScala) { @@ -526,7 +532,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) val validatedRecords = validatedResults.validatedRecords for (batch <- validatedRecords.batches.asScala) { @@ -583,7 +590,8 @@ class LogValidatorTest { partitionLeaderEpoch = partitionLeaderEpoch, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) val validatedRecords = validatedResults.validatedRecords var i = 0 @@ -636,7 +644,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats)) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching)) } @Test @@ -659,7 +668,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats)) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching)) } @Test @@ -682,7 +692,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats)) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching)) } @Test @@ -705,7 +716,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats)) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching)) } @Test @@ -727,7 +739,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords, offset) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords, offset) } @Test @@ -749,7 +762,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords, offset) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords, offset) } @Test @@ -772,7 +786,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords checkOffsets(messageWithOffset, offset) } @@ -796,7 +811,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords checkOffsets(messageWithOffset, offset) } @@ -821,7 +837,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords checkOffsets(compressedMessagesWithOffset, offset) } @@ -846,7 +863,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords checkOffsets(compressedMessagesWithOffset, offset) } @@ -869,7 +887,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) checkOffsets(validatedResults.validatedRecords, offset) verifyRecordConversionStats(validatedResults.recordConversionStats, numConvertedRecords = 3, records, compressed = false) @@ -894,7 +913,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) checkOffsets(validatedResults.validatedRecords, offset) verifyRecordConversionStats(validatedResults.recordConversionStats, numConvertedRecords = 3, records, compressed = false) @@ -919,7 +939,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) checkOffsets(validatedResults.validatedRecords, offset) verifyRecordConversionStats(validatedResults.recordConversionStats, numConvertedRecords = 3, records, compressed = true) @@ -944,7 +965,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) checkOffsets(validatedResults.validatedRecords, offset) verifyRecordConversionStats(validatedResults.recordConversionStats, numConvertedRecords = 3, records, compressed = true) @@ -969,7 +991,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats)) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching)) } @Test @@ -991,7 +1014,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Coordinator, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) val batches = TestUtils.toList(result.validatedRecords.batches) assertEquals(1, batches.size) val batch = batches.get(0) @@ -1018,7 +1042,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords, offset) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords, offset) } @Test @@ -1041,7 +1066,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords, offset) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords, offset) } @Test @@ -1063,7 +1089,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords, offset) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords, offset) } @Test @@ -1085,7 +1112,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords, offset) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords, offset) } @Test @@ -1108,7 +1136,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords, offset) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords, offset) } @Test @@ -1131,7 +1160,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords, offset) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords, offset) } @Test @@ -1156,7 +1186,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats)) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching)) } @Test @@ -1181,7 +1212,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats)) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching)) } @Test @@ -1204,7 +1236,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords, offset) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords, offset) } @Test @@ -1227,7 +1260,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats).validatedRecords, offset) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching).validatedRecords, offset) } @Test @@ -1248,7 +1282,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) ) assertEquals(metricsKeySet.count(_.getMBeanName.endsWith(s"${BrokerTopicStats.InvalidOffsetOrSequenceRecordsPerSec}")), 1) assertTrue(meterCount(s"${BrokerTopicStats.InvalidOffsetOrSequenceRecordsPerSec}") > 0) @@ -1278,7 +1313,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = KAFKA_2_0_IV1, - brokerTopicStats = brokerTopicStats)) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching)) } @Test @@ -1312,7 +1348,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching) ) assertTrue(e.invalidException.isInstanceOf[InvalidTimestampException]) @@ -1390,7 +1427,8 @@ class LogValidatorTest { partitionLeaderEpoch = RecordBatch.NO_PARTITION_LEADER_EPOCH, origin = AppendOrigin.Client, interBrokerProtocolVersion = ApiVersion.latestVersion, - brokerTopicStats = brokerTopicStats)) + brokerTopicStats = brokerTopicStats, + requestLocal = RequestLocal.withThreadConfinedCaching)) } private def createRecords(magicValue: Byte, diff --git a/core/src/test/scala/unit/kafka/server/ControllerApisTest.scala b/core/src/test/scala/unit/kafka/server/ControllerApisTest.scala index b0fa2b36b940f..89fbd0526e690 100644 --- a/core/src/test/scala/unit/kafka/server/ControllerApisTest.scala +++ b/core/src/test/scala/unit/kafka/server/ControllerApisTest.scala @@ -274,7 +274,8 @@ class ControllerApisTest { val request = buildRequest(brokerRegistrationRequest) val capturedResponse: ArgumentCaptor[AbstractResponse] = ArgumentCaptor.forClass(classOf[AbstractResponse]) - createControllerApis(Some(createDenyAllAuthorizer()), mock(classOf[Controller])).handle(request) + createControllerApis(Some(createDenyAllAuthorizer()), mock(classOf[Controller])).handle(request, + RequestLocal.withThreadConfinedCaching) verify(requestChannel).sendResponse( ArgumentMatchers.eq(request), capturedResponse.capture(), @@ -409,6 +410,14 @@ class ControllerApisTest { new AlterPartitionReassignmentsRequestData()).build()))) } + @Test + def testUnauthorizedHandleAllocateProducerIds(): Unit = { + assertThrows(classOf[ClusterAuthorizationException], () => createControllerApis( + Some(createDenyAllAuthorizer()), new MockController.Builder().build()). + handleAllocateProducerIdsRequest(buildRequest(new AllocateProducerIdsRequest.Builder( + new AllocateProducerIdsRequestData()).build()))) + } + @Test def testUnauthorizedHandleListPartitionReassignments(): Unit = { assertThrows(classOf[ClusterAuthorizationException], () => createControllerApis( diff --git a/core/src/test/scala/unit/kafka/server/KafkaApisTest.scala b/core/src/test/scala/unit/kafka/server/KafkaApisTest.scala index bd5de0838b720..9a0bdb04211ca 100644 --- a/core/src/test/scala/unit/kafka/server/KafkaApisTest.scala +++ b/core/src/test/scala/unit/kafka/server/KafkaApisTest.scala @@ -313,7 +313,7 @@ class KafkaApisTest { EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel, authorizer, adminManager, controller) - createKafkaApis(authorizer = Some(authorizer), enableForwarding = true).handle(request) + createKafkaApis(authorizer = Some(authorizer), enableForwarding = true).handle(request, RequestLocal.withThreadConfinedCaching) assertEquals(Some(request), capturedRequest.getValue.envelope) val innerResponse = capturedResponse.getValue.asInstanceOf[AlterConfigsResponse] @@ -339,7 +339,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(request) EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel, controller) - createKafkaApis(enableForwarding = true).handle(request) + createKafkaApis(enableForwarding = true).handle(request, RequestLocal.withThreadConfinedCaching) val response = capturedResponse.getValue.asInstanceOf[EnvelopeResponse] assertEquals(Errors.INVALID_REQUEST, response.error()) @@ -407,7 +407,7 @@ class KafkaApisTest { EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel, authorizer, adminManager, controller) - createKafkaApis(authorizer = Some(authorizer), enableForwarding = true).handle(request) + createKafkaApis(authorizer = Some(authorizer), enableForwarding = true).handle(request, RequestLocal.withThreadConfinedCaching) if (!shouldCloseConnection) { val response = capturedResponse.getValue.asInstanceOf[EnvelopeResponse] @@ -482,7 +482,7 @@ class KafkaApisTest { EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel, controller, forwardingManager) - createKafkaApis(enableForwarding = true).handle(request) + createKafkaApis(enableForwarding = true).handle(request, RequestLocal.withThreadConfinedCaching) EasyMock.verify(controller, forwardingManager) } @@ -1078,7 +1078,7 @@ class KafkaApisTest { val request = buildRequest(offsetCommitRequest) val capturedResponse = expectNoThrottling(request) EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel) - createKafkaApis().handleOffsetCommitRequest(request) + createKafkaApis().handleOffsetCommitRequest(request, RequestLocal.withThreadConfinedCaching) val response = capturedResponse.getValue.asInstanceOf[OffsetCommitResponse] assertEquals(Errors.UNKNOWN_TOPIC_OR_PARTITION, @@ -1110,7 +1110,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(request) EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel) - createKafkaApis().handleTxnOffsetCommitRequest(request) + createKafkaApis().handleTxnOffsetCommitRequest(request, RequestLocal.withThreadConfinedCaching) val response = capturedResponse.getValue.asInstanceOf[TxnOffsetCommitResponse] assertEquals(Errors.UNKNOWN_TOPIC_OR_PARTITION, response.errors().get(invalidTopicPartition)) @@ -1147,6 +1147,7 @@ class KafkaApisTest { ).build(version.toShort) val request = buildRequest(offsetCommitRequest) + val requestLocal = RequestLocal.withThreadConfinedCaching EasyMock.expect(groupCoordinator.handleTxnCommitOffsets( EasyMock.eq(groupId), EasyMock.eq(producerId), @@ -1155,7 +1156,8 @@ class KafkaApisTest { EasyMock.eq(Option.empty), EasyMock.anyInt(), EasyMock.anyObject(), - EasyMock.capture(responseCallback) + EasyMock.capture(responseCallback), + EasyMock.eq(requestLocal) )).andAnswer( () => responseCallback.getValue.apply(Map(topicPartition -> Errors.COORDINATOR_LOAD_IN_PROGRESS))) @@ -1167,7 +1169,7 @@ class KafkaApisTest { EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel, groupCoordinator) - createKafkaApis().handleTxnOffsetCommitRequest(request) + createKafkaApis().handleTxnOffsetCommitRequest(request, requestLocal) val response = capturedResponse.getValue.asInstanceOf[TxnOffsetCommitResponse] @@ -1219,11 +1221,13 @@ class KafkaApisTest { else Option(new ProducerIdAndEpoch(producerId, epoch)) + val requestLocal = RequestLocal.withThreadConfinedCaching EasyMock.expect(txnCoordinator.handleInitProducerId( EasyMock.eq(transactionalId), EasyMock.eq(txnTimeoutMs), EasyMock.eq(expectedProducerIdAndEpoch), - EasyMock.capture(responseCallback) + EasyMock.capture(responseCallback), + EasyMock.eq(requestLocal) )).andAnswer( () => responseCallback.getValue.apply(InitProducerIdResult(producerId, epoch, Errors.PRODUCER_FENCED))) @@ -1235,7 +1239,7 @@ class KafkaApisTest { EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel, txnCoordinator) - createKafkaApis().handleInitProducerIdRequest(request) + createKafkaApis().handleInitProducerIdRequest(request, requestLocal) val response = capturedResponse.getValue.asInstanceOf[InitProducerIdResponse] @@ -1278,12 +1282,14 @@ class KafkaApisTest { EasyMock.eq(groupId) )).andReturn(partition) + val requestLocal = RequestLocal.withThreadConfinedCaching EasyMock.expect(txnCoordinator.handleAddPartitionsToTransaction( EasyMock.eq(transactionalId), EasyMock.eq(producerId), EasyMock.eq(epoch), EasyMock.eq(Set(new TopicPartition(Topic.GROUP_METADATA_TOPIC_NAME, partition))), - EasyMock.capture(responseCallback) + EasyMock.capture(responseCallback), + EasyMock.eq(requestLocal) )).andAnswer( () => responseCallback.getValue.apply(Errors.PRODUCER_FENCED)) @@ -1295,7 +1301,7 @@ class KafkaApisTest { EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel, txnCoordinator, groupCoordinator) - createKafkaApis().handleAddOffsetsToTxnRequest(request) + createKafkaApis().handleAddOffsetsToTxnRequest(request, requestLocal) val response = capturedResponse.getValue.asInstanceOf[AddOffsetsToTxnResponse] @@ -1334,13 +1340,14 @@ class KafkaApisTest { ).build(version.toShort) val request = buildRequest(addPartitionsToTxnRequest) + val requestLocal = RequestLocal.withThreadConfinedCaching EasyMock.expect(txnCoordinator.handleAddPartitionsToTransaction( EasyMock.eq(transactionalId), EasyMock.eq(producerId), EasyMock.eq(epoch), EasyMock.eq(Set(topicPartition)), - - EasyMock.capture(responseCallback) + EasyMock.capture(responseCallback), + EasyMock.eq(requestLocal) )).andAnswer( () => responseCallback.getValue.apply(Errors.PRODUCER_FENCED)) @@ -1352,7 +1359,7 @@ class KafkaApisTest { EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel, txnCoordinator) - createKafkaApis().handleAddPartitionToTxnRequest(request) + createKafkaApis().handleAddPartitionToTxnRequest(request, requestLocal) val response = capturedResponse.getValue.asInstanceOf[AddPartitionsToTxnResponse] @@ -1388,12 +1395,14 @@ class KafkaApisTest { ).build(version.toShort) val request = buildRequest(endTxnRequest) + val requestLocal = RequestLocal.withThreadConfinedCaching EasyMock.expect(txnCoordinator.handleEndTransaction( EasyMock.eq(transactionalId), EasyMock.eq(producerId), EasyMock.eq(epoch), EasyMock.eq(TransactionResult.COMMIT), - EasyMock.capture(responseCallback) + EasyMock.capture(responseCallback), + EasyMock.eq(requestLocal) )).andAnswer( () => responseCallback.getValue.apply(Errors.PRODUCER_FENCED)) @@ -1404,7 +1413,7 @@ class KafkaApisTest { )) EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel, txnCoordinator) - createKafkaApis().handleEndTxnRequest(request) + createKafkaApis().handleEndTxnRequest(request, requestLocal) val response = capturedResponse.getValue.asInstanceOf[EndTxnResponse] @@ -1449,6 +1458,7 @@ class KafkaApisTest { EasyMock.anyObject(), EasyMock.capture(responseCallback), EasyMock.anyObject(), + EasyMock.anyObject(), EasyMock.anyObject()) ).andAnswer(() => responseCallback.getValue.apply(Map(tp -> new PartitionResponse(Errors.INVALID_PRODUCER_EPOCH)))) @@ -1458,7 +1468,7 @@ class KafkaApisTest { EasyMock.replay(replicaManager, clientQuotaManager, clientRequestQuotaManager, requestChannel, txnCoordinator) - createKafkaApis().handleProduceRequest(request) + createKafkaApis().handleProduceRequest(request, RequestLocal.withThreadConfinedCaching) val response = capturedResponse.getValue.asInstanceOf[ProduceResponse] @@ -1486,7 +1496,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(request) EasyMock.replay(replicaManager, clientRequestQuotaManager, requestChannel) - createKafkaApis().handleAddPartitionToTxnRequest(request) + createKafkaApis().handleAddPartitionToTxnRequest(request, RequestLocal.withThreadConfinedCaching) val response = capturedResponse.getValue.asInstanceOf[AddPartitionsToTxnResponse] assertEquals(Errors.UNKNOWN_TOPIC_OR_PARTITION, response.errors().get(invalidTopicPartition)) @@ -1498,27 +1508,32 @@ class KafkaApisTest { @Test def shouldThrowUnsupportedVersionExceptionOnHandleAddOffsetToTxnRequestWhenInterBrokerProtocolNotSupported(): Unit = { - assertThrows(classOf[UnsupportedVersionException], () => createKafkaApis(KAFKA_0_10_2_IV0).handleAddOffsetsToTxnRequest(null)) + assertThrows(classOf[UnsupportedVersionException], + () => createKafkaApis(KAFKA_0_10_2_IV0).handleAddOffsetsToTxnRequest(null, RequestLocal.withThreadConfinedCaching)) } @Test def shouldThrowUnsupportedVersionExceptionOnHandleAddPartitionsToTxnRequestWhenInterBrokerProtocolNotSupported(): Unit = { - assertThrows(classOf[UnsupportedVersionException], () => createKafkaApis(KAFKA_0_10_2_IV0).handleAddPartitionToTxnRequest(null)) + assertThrows(classOf[UnsupportedVersionException], + () => createKafkaApis(KAFKA_0_10_2_IV0).handleAddPartitionToTxnRequest(null, RequestLocal.withThreadConfinedCaching)) } @Test def shouldThrowUnsupportedVersionExceptionOnHandleTxnOffsetCommitRequestWhenInterBrokerProtocolNotSupported(): Unit = { - assertThrows(classOf[UnsupportedVersionException], () => createKafkaApis(KAFKA_0_10_2_IV0).handleAddPartitionToTxnRequest(null)) + assertThrows(classOf[UnsupportedVersionException], + () => createKafkaApis(KAFKA_0_10_2_IV0).handleAddPartitionToTxnRequest(null, RequestLocal.withThreadConfinedCaching)) } @Test def shouldThrowUnsupportedVersionExceptionOnHandleEndTxnRequestWhenInterBrokerProtocolNotSupported(): Unit = { - assertThrows(classOf[UnsupportedVersionException], () => createKafkaApis(KAFKA_0_10_2_IV0).handleEndTxnRequest(null)) + assertThrows(classOf[UnsupportedVersionException], + () => createKafkaApis(KAFKA_0_10_2_IV0).handleEndTxnRequest(null, RequestLocal.withThreadConfinedCaching)) } @Test def shouldThrowUnsupportedVersionExceptionOnHandleWriteTxnMarkersRequestWhenInterBrokerProtocolNotSupported(): Unit = { - assertThrows(classOf[UnsupportedVersionException], () => createKafkaApis(KAFKA_0_10_2_IV0).handleWriteTxnMarkersRequest(null)) + assertThrows(classOf[UnsupportedVersionException], + () => createKafkaApis(KAFKA_0_10_2_IV0).handleWriteTxnMarkersRequest(null, RequestLocal.withThreadConfinedCaching)) } @Test @@ -1537,7 +1552,7 @@ class KafkaApisTest { )) EasyMock.replay(replicaManager, replicaQuotaManager, requestChannel) - createKafkaApis().handleWriteTxnMarkersRequest(request) + createKafkaApis().handleWriteTxnMarkersRequest(request, RequestLocal.withThreadConfinedCaching) val markersResponse = capturedResponse.getValue.asInstanceOf[WriteTxnMarkersResponse] assertEquals(expectedErrors, markersResponse.errorsByProducerId.get(1L)) @@ -1559,7 +1574,7 @@ class KafkaApisTest { )) EasyMock.replay(replicaManager, replicaQuotaManager, requestChannel) - createKafkaApis().handleWriteTxnMarkersRequest(request) + createKafkaApis().handleWriteTxnMarkersRequest(request, RequestLocal.withThreadConfinedCaching) val markersResponse = capturedResponse.getValue.asInstanceOf[WriteTxnMarkersResponse] assertEquals(expectedErrors, markersResponse.errorsByProducerId.get(1L)) @@ -1580,6 +1595,7 @@ class KafkaApisTest { EasyMock.expect(replicaManager.getMagic(tp2)) .andReturn(Some(RecordBatch.MAGIC_VALUE_V2)) + val requestLocal = RequestLocal.withThreadConfinedCaching EasyMock.expect(replicaManager.appendRecords(EasyMock.anyLong(), EasyMock.anyShort(), EasyMock.eq(true), @@ -1587,7 +1603,8 @@ class KafkaApisTest { EasyMock.anyObject(), EasyMock.capture(responseCallback), EasyMock.anyObject(), - EasyMock.anyObject()) + EasyMock.anyObject(), + EasyMock.eq(requestLocal)) ).andAnswer(() => responseCallback.getValue.apply(Map(tp2 -> new PartitionResponse(Errors.NONE)))) EasyMock.expect(requestChannel.sendResponse( @@ -1597,7 +1614,7 @@ class KafkaApisTest { )) EasyMock.replay(replicaManager, replicaQuotaManager, requestChannel) - createKafkaApis().handleWriteTxnMarkersRequest(request) + createKafkaApis().handleWriteTxnMarkersRequest(request, requestLocal) val markersResponse = capturedResponse.getValue.asInstanceOf[WriteTxnMarkersResponse] assertEquals(expectedErrors, markersResponse.errorsByProducerId.get(1L)) @@ -1693,7 +1710,11 @@ class KafkaApisTest { } if (deletePartition) { - groupCoordinator.onResignation(groupMetadataPartition.partition) + if (leaderEpoch >= 0) { + groupCoordinator.onResignation(groupMetadataPartition.partition, Some(leaderEpoch)) + } else { + groupCoordinator.onResignation(groupMetadataPartition.partition, None) + } EasyMock.expectLastCall() } @@ -1719,6 +1740,7 @@ class KafkaApisTest { EasyMock.expect(replicaManager.getMagic(tp2)) .andReturn(Some(RecordBatch.MAGIC_VALUE_V2)) + val requestLocal = RequestLocal.withThreadConfinedCaching EasyMock.expect(replicaManager.appendRecords(EasyMock.anyLong(), EasyMock.anyShort(), EasyMock.eq(true), @@ -1726,7 +1748,8 @@ class KafkaApisTest { EasyMock.anyObject(), EasyMock.capture(responseCallback), EasyMock.anyObject(), - EasyMock.anyObject()) + EasyMock.anyObject(), + EasyMock.eq(requestLocal)) ).andAnswer(() => responseCallback.getValue.apply(Map(tp2 -> new PartitionResponse(Errors.NONE)))) EasyMock.expect(requestChannel.sendResponse( @@ -1736,7 +1759,7 @@ class KafkaApisTest { )) EasyMock.replay(replicaManager, replicaQuotaManager, requestChannel) - createKafkaApis().handleWriteTxnMarkersRequest(request) + createKafkaApis().handleWriteTxnMarkersRequest(request, requestLocal) val markersResponse = capturedResponse.getValue.asInstanceOf[WriteTxnMarkersResponse] assertEquals(expectedErrors, markersResponse.errorsByProducerId.get(1L)) @@ -1750,6 +1773,7 @@ class KafkaApisTest { EasyMock.expect(replicaManager.getMagic(topicPartition)) .andReturn(Some(RecordBatch.MAGIC_VALUE_V2)) + val requestLocal = RequestLocal.withThreadConfinedCaching EasyMock.expect(replicaManager.appendRecords(EasyMock.anyLong(), EasyMock.anyShort(), EasyMock.eq(true), @@ -1757,11 +1781,12 @@ class KafkaApisTest { EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject(), - EasyMock.anyObject())) + EasyMock.anyObject(), + EasyMock.eq(requestLocal))) EasyMock.replay(replicaManager) - createKafkaApis().handleWriteTxnMarkersRequest(request) + createKafkaApis().handleWriteTxnMarkersRequest(request, requestLocal) EasyMock.verify(replicaManager) } @@ -1857,6 +1882,7 @@ class KafkaApisTest { ).build() val request = buildRequest(offsetDeleteRequest) + val requestLocal = RequestLocal.withThreadConfinedCaching val capturedResponse = expectNoThrottling(request) EasyMock.expect(groupCoordinator.handleDeleteOffsets( EasyMock.eq(group), @@ -1865,7 +1891,8 @@ class KafkaApisTest { new TopicPartition("topic-1", 1), new TopicPartition("topic-2", 0), new TopicPartition("topic-2", 1) - )) + )), + EasyMock.eq(requestLocal) )).andReturn((Errors.NONE, Map( new TopicPartition("topic-1", 0) -> Errors.NONE, new TopicPartition("topic-1", 1) -> Errors.NONE, @@ -1875,7 +1902,7 @@ class KafkaApisTest { EasyMock.replay(groupCoordinator, replicaManager, clientRequestQuotaManager, requestChannel) - createKafkaApis().handleOffsetDeleteRequest(request) + createKafkaApis().handleOffsetDeleteRequest(request, requestLocal) val response = capturedResponse.getValue.asInstanceOf[OffsetDeleteResponse] @@ -1912,11 +1939,12 @@ class KafkaApisTest { val request = buildRequest(offsetDeleteRequest) val capturedResponse = expectNoThrottling(request) - EasyMock.expect(groupCoordinator.handleDeleteOffsets(EasyMock.eq(group), EasyMock.eq(Seq.empty))) - .andReturn((Errors.NONE, Map.empty)) + val requestLocal = RequestLocal.withThreadConfinedCaching + EasyMock.expect(groupCoordinator.handleDeleteOffsets(EasyMock.eq(group), EasyMock.eq(Seq.empty), + EasyMock.eq(requestLocal))).andReturn((Errors.NONE, Map.empty)) EasyMock.replay(groupCoordinator, replicaManager, clientRequestQuotaManager, requestChannel) - createKafkaApis().handleOffsetDeleteRequest(request) + createKafkaApis().handleOffsetDeleteRequest(request, requestLocal) val response = capturedResponse.getValue.asInstanceOf[OffsetDeleteResponse] @@ -1941,11 +1969,12 @@ class KafkaApisTest { val request = buildRequest(offsetDeleteRequest) val capturedResponse = expectNoThrottling(request) - EasyMock.expect(groupCoordinator.handleDeleteOffsets(EasyMock.eq(group), EasyMock.eq(Seq.empty))) - .andReturn((Errors.GROUP_ID_NOT_FOUND, Map.empty)) + val requestLocal = RequestLocal.withThreadConfinedCaching + EasyMock.expect(groupCoordinator.handleDeleteOffsets(EasyMock.eq(group), EasyMock.eq(Seq.empty), + EasyMock.eq(requestLocal))).andReturn((Errors.GROUP_ID_NOT_FOUND, Map.empty)) EasyMock.replay(groupCoordinator, replicaManager, clientRequestQuotaManager, requestChannel) - createKafkaApis().handleOffsetDeleteRequest(request) + createKafkaApis().handleOffsetDeleteRequest(request, requestLocal) val response = capturedResponse.getValue.asInstanceOf[OffsetDeleteResponse] @@ -2174,6 +2203,7 @@ class KafkaApisTest { EasyMock.eq(sessionTimeoutMs), EasyMock.eq(protocolType), EasyMock.capture(capturedProtocols), + anyObject(), anyObject() )) @@ -2193,7 +2223,8 @@ class KafkaApisTest { .setName(name).setMetadata(protocol) }.iterator.asJava)) ).build() - )) + ), + RequestLocal.withThreadConfinedCaching) EasyMock.verify(groupCoordinator) @@ -2234,7 +2265,8 @@ class KafkaApisTest { EasyMock.eq(sessionTimeoutMs), EasyMock.eq(protocolType), EasyMock.eq(List.empty), - EasyMock.capture(capturedCallback) + EasyMock.capture(capturedCallback), + EasyMock.anyObject() )) val joinGroupRequest = new JoinGroupRequest.Builder( @@ -2250,7 +2282,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(requestChannelRequest) EasyMock.replay(groupCoordinator, clientRequestQuotaManager, requestChannel, replicaManager) - createKafkaApis().handleJoinGroupRequest(requestChannelRequest) + createKafkaApis().handleJoinGroupRequest(requestChannelRequest, RequestLocal.withThreadConfinedCaching) EasyMock.verify(groupCoordinator) @@ -2304,7 +2336,8 @@ class KafkaApisTest { EasyMock.eq(sessionTimeoutMs), EasyMock.eq(protocolType), EasyMock.eq(List.empty), - EasyMock.capture(capturedCallback) + EasyMock.capture(capturedCallback), + EasyMock.anyObject() )) val joinGroupRequest = new JoinGroupRequest.Builder( @@ -2320,7 +2353,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(requestChannelRequest) EasyMock.replay(groupCoordinator, clientRequestQuotaManager, requestChannel, replicaManager) - createKafkaApis().handleJoinGroupRequest(requestChannelRequest) + createKafkaApis().handleJoinGroupRequest(requestChannelRequest, RequestLocal.withThreadConfinedCaching) EasyMock.verify(groupCoordinator) @@ -2364,6 +2397,7 @@ class KafkaApisTest { val capturedCallback = EasyMock.newCapture[SyncGroupCallback]() + val requestLocal = RequestLocal.withThreadConfinedCaching EasyMock.expect(groupCoordinator.handleSyncGroup( EasyMock.eq(groupId), EasyMock.eq(0), @@ -2372,7 +2406,8 @@ class KafkaApisTest { EasyMock.eq(if (version >= 5) Some(protocolName) else None), EasyMock.eq(None), EasyMock.eq(Map.empty), - EasyMock.capture(capturedCallback) + EasyMock.capture(capturedCallback), + EasyMock.eq(requestLocal) )) val syncGroupRequest = new SyncGroupRequest.Builder( @@ -2388,7 +2423,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(requestChannelRequest) EasyMock.replay(groupCoordinator, clientRequestQuotaManager, requestChannel, replicaManager) - createKafkaApis().handleSyncGroupRequest(requestChannelRequest) + createKafkaApis().handleSyncGroupRequest(requestChannelRequest, requestLocal) EasyMock.verify(groupCoordinator) @@ -2425,6 +2460,7 @@ class KafkaApisTest { val capturedCallback = EasyMock.newCapture[SyncGroupCallback]() + val requestLocal = RequestLocal.withThreadConfinedCaching if (version < 5) { EasyMock.expect(groupCoordinator.handleSyncGroup( EasyMock.eq(groupId), @@ -2434,7 +2470,8 @@ class KafkaApisTest { EasyMock.eq(None), EasyMock.eq(None), EasyMock.eq(Map.empty), - EasyMock.capture(capturedCallback) + EasyMock.capture(capturedCallback), + EasyMock.eq(requestLocal) )) } @@ -2449,7 +2486,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(requestChannelRequest) EasyMock.replay(groupCoordinator, clientRequestQuotaManager, requestChannel, replicaManager) - createKafkaApis().handleSyncGroupRequest(requestChannelRequest) + createKafkaApis().handleSyncGroupRequest(requestChannelRequest, requestLocal) EasyMock.verify(groupCoordinator) @@ -2488,7 +2525,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(requestChannelRequest) EasyMock.replay(clientRequestQuotaManager, requestChannel) - createKafkaApis(KAFKA_2_2_IV1).handleJoinGroupRequest(requestChannelRequest) + createKafkaApis(KAFKA_2_2_IV1).handleJoinGroupRequest(requestChannelRequest, RequestLocal.withThreadConfinedCaching) val response = capturedResponse.getValue.asInstanceOf[JoinGroupResponse] assertEquals(Errors.UNSUPPORTED_VERSION, response.error()) @@ -2509,7 +2546,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(requestChannelRequest) EasyMock.replay(clientRequestQuotaManager, requestChannel) - createKafkaApis(KAFKA_2_2_IV1).handleSyncGroupRequest(requestChannelRequest) + createKafkaApis(KAFKA_2_2_IV1).handleSyncGroupRequest(requestChannelRequest, RequestLocal.withThreadConfinedCaching) val response = capturedResponse.getValue.asInstanceOf[SyncGroupResponse] assertEquals(Errors.UNSUPPORTED_VERSION, response.error) @@ -2561,7 +2598,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(requestChannelRequest) EasyMock.replay(clientRequestQuotaManager, requestChannel) - createKafkaApis(KAFKA_2_2_IV1).handleOffsetCommitRequest(requestChannelRequest) + createKafkaApis(KAFKA_2_2_IV1).handleOffsetCommitRequest(requestChannelRequest, RequestLocal.withThreadConfinedCaching) val expectedTopicErrors = Collections.singletonList( new OffsetCommitResponseData.OffsetCommitResponseTopic() @@ -2688,7 +2725,7 @@ class KafkaApisTest { replay(replicaManager, fetchManager, clientQuotaManager, requestChannel, replicaQuotaManager, partition) - createKafkaApis().handle(fetchFromFollower) + createKafkaApis().handle(fetchFromFollower, RequestLocal.withThreadConfinedCaching) if (isReassigning) assertEquals(records.sizeInBytes(), brokerTopicStats.allTopicsStats.reassignmentBytesOutPerSec.get.count()) @@ -2712,7 +2749,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(requestChannelRequest) EasyMock.replay(clientRequestQuotaManager, requestChannel) - createKafkaApis(KAFKA_2_2_IV1).handleInitProducerIdRequest(requestChannelRequest) + createKafkaApis(KAFKA_2_2_IV1).handleInitProducerIdRequest(requestChannelRequest, RequestLocal.withThreadConfinedCaching) val response = capturedResponse.getValue.asInstanceOf[InitProducerIdResponse] assertEquals(Errors.INVALID_REQUEST, response.error) @@ -2731,7 +2768,7 @@ class KafkaApisTest { val capturedResponse = expectNoThrottling(requestChannelRequest) EasyMock.replay(clientRequestQuotaManager, requestChannel) - createKafkaApis(KAFKA_2_2_IV1).handleInitProducerIdRequest(requestChannelRequest) + createKafkaApis(KAFKA_2_2_IV1).handleInitProducerIdRequest(requestChannelRequest, RequestLocal.withThreadConfinedCaching) val response = capturedResponse.getValue.asInstanceOf[InitProducerIdResponse] assertEquals(Errors.INVALID_REQUEST, response.error) @@ -2776,7 +2813,7 @@ class KafkaApisTest { )) EasyMock.replay(replicaManager, controller, requestChannel) - createKafkaApis().handleUpdateMetadataRequest(request) + createKafkaApis().handleUpdateMetadataRequest(request, RequestLocal.withThreadConfinedCaching) val updateMetadataResponse = capturedResponse.getValue.asInstanceOf[UpdateMetadataResponse] assertEquals(expectedError, updateMetadataResponse.error()) EasyMock.verify(replicaManager) @@ -3754,7 +3791,7 @@ class KafkaApisTest { @Test def testRaftShouldNeverHandleUpdateMetadataRequest(): Unit = { metadataCache = MetadataCache.raftMetadataCache(brokerId) - verifyShouldNeverHandle(createKafkaApis(raftSupport = true).handleUpdateMetadataRequest) + verifyShouldNeverHandle(createKafkaApis(raftSupport = true).handleUpdateMetadataRequest(_, RequestLocal.withThreadConfinedCaching)) } @Test @@ -3772,7 +3809,7 @@ class KafkaApisTest { @Test def testRaftShouldNeverHandleEnvelope(): Unit = { metadataCache = MetadataCache.raftMetadataCache(brokerId) - verifyShouldNeverHandle(createKafkaApis(raftSupport = true).handleEnvelope) + verifyShouldNeverHandle(createKafkaApis(raftSupport = true).handleEnvelope(_, RequestLocal.withThreadConfinedCaching)) } @Test diff --git a/core/src/test/scala/unit/kafka/server/ReplicaManagerTest.scala b/core/src/test/scala/unit/kafka/server/ReplicaManagerTest.scala index 28f7be88c0426..65ab81f04230d 100644 --- a/core/src/test/scala/unit/kafka/server/ReplicaManagerTest.scala +++ b/core/src/test/scala/unit/kafka/server/ReplicaManagerTest.scala @@ -1075,7 +1075,6 @@ class ReplicaManagerTest { private def initializeLogAndTopicId(replicaManager: ReplicaManager, topicPartition: TopicPartition, topicId: Uuid): Unit = { val partition = replicaManager.createPartition(new TopicPartition(topic, 0)) val log = replicaManager.logManager.getOrCreateLog(topicPartition, false, false, Some(topicId)) - log.topicId = Some(topicId) partition.log = Some(log) } @@ -1517,7 +1516,7 @@ class ReplicaManagerTest { leaderEpochCache = leaderEpochCache, producerStateManager = producerStateManager, logDirFailureChannel = mockLogDirFailureChannel, - topicId = topicId, + _topicId = topicId, keepPartitionMetadataFile = true) { override def endOffsetForEpoch(leaderEpoch: Int): Option[OffsetAndEpoch] = { @@ -2195,7 +2194,7 @@ class ReplicaManagerTest { val batch = TestUtils.records(records = List( new SimpleRecord(10, "k1".getBytes, "v1".getBytes), new SimpleRecord(11, "k2".getBytes, "v2".getBytes))) - partition.appendRecordsToLeader(batch, AppendOrigin.Client, requiredAcks = 0) + partition.appendRecordsToLeader(batch, AppendOrigin.Client, requiredAcks = 0, RequestLocal.withThreadConfinedCaching) partition.log.get.updateHighWatermark(2L) partition.log.get.maybeIncrementLogStartOffset(1L, LeaderOffsetIncremented) replicaManager.logManager.checkpointLogRecoveryOffsets() diff --git a/core/src/test/scala/unit/kafka/server/metadata/BrokerMetadataListenerTest.scala b/core/src/test/scala/unit/kafka/server/metadata/BrokerMetadataListenerTest.scala index 89ba5f1d03328..f48a75d48dc45 100644 --- a/core/src/test/scala/unit/kafka/server/metadata/BrokerMetadataListenerTest.scala +++ b/core/src/test/scala/unit/kafka/server/metadata/BrokerMetadataListenerTest.scala @@ -94,7 +94,7 @@ class BrokerMetadataListenerTest { verify(groupCoordinator).handleDeletedPartitions(ArgumentMatchers.argThat[Seq[TopicPartition]] { partitions => partitions.toSet == partitionSet(topic, numPartitions) - }) + }, any()) val deleteImageCapture: ArgumentCaptor[MetadataImageBuilder] = ArgumentCaptor.forClass(classOf[MetadataImageBuilder]) diff --git a/core/src/test/scala/unit/kafka/utils/SchedulerTest.scala b/core/src/test/scala/unit/kafka/utils/SchedulerTest.scala index e5d2f2176c393..b085ab0166513 100644 --- a/core/src/test/scala/unit/kafka/utils/SchedulerTest.scala +++ b/core/src/test/scala/unit/kafka/utils/SchedulerTest.scala @@ -143,7 +143,7 @@ class SchedulerTest { recoveryPoint = offsets.recoveryPoint, nextOffsetMetadata = offsets.nextOffsetMetadata, scheduler, brokerTopicStats, mockTime, LogManager.ProducerIdExpirationCheckIntervalMs, topicPartition, leaderEpochCache, producerStateManager, logDirFailureChannel, - topicId = None, keepPartitionMetadataFile = true) + _topicId = None, keepPartitionMetadataFile = true) assertTrue(scheduler.taskRunning(log.producerExpireCheck)) log.close() assertFalse(scheduler.taskRunning(log.producerExpireCheck)) diff --git a/core/src/test/scala/unit/kafka/zk/KafkaZkClientTest.scala b/core/src/test/scala/unit/kafka/zk/KafkaZkClientTest.scala index 27c15a9dc1f79..da10152f64ac6 100644 --- a/core/src/test/scala/unit/kafka/zk/KafkaZkClientTest.scala +++ b/core/src/test/scala/unit/kafka/zk/KafkaZkClientTest.scala @@ -16,9 +16,11 @@ */ package kafka.zk -import java.util.{Collections, Properties} +import java.util.{Base64, Collections, Properties} import java.nio.charset.StandardCharsets.UTF_8 +import java.security.MessageDigest import java.util.concurrent.{CountDownLatch, TimeUnit} + import kafka.api.{ApiVersion, LeaderAndIsr} import kafka.cluster.{Broker, EndPoint} import kafka.log.LogConfig @@ -51,7 +53,7 @@ import org.apache.kafka.common.resource.ResourceType.{GROUP, TOPIC} import org.apache.kafka.common.security.JaasUtils import org.apache.zookeeper.ZooDefs import org.apache.zookeeper.client.ZKClientConfig -import org.apache.zookeeper.data.Stat +import org.apache.zookeeper.data.{ACL, Id, Stat} import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource @@ -140,6 +142,28 @@ class KafkaZkClientTest extends ZooKeeperTestHarness { } } + @Test + def testChrootExistsAndRootIsLocked(): Unit = { + // chroot is accessible + val root = "/testChrootExistsAndRootIsLocked" + val chroot = s"$root/chroot" + zkClient.makeSurePersistentPathExists(chroot) + zkClient.setAcl(chroot, ZooDefs.Ids.OPEN_ACL_UNSAFE.asScala) + + // root is inaccessible + val scheme = "digest" + val id = "test" + val pwd = "12345" + val digest = Base64.getEncoder.encode(MessageDigest.getInstance("SHA1").digest(s"$id:$pwd".getBytes())) + zkClient.currentZooKeeper.addAuthInfo(scheme, digest) + zkClient.setAcl(root, Seq(new ACL(ZooDefs.Perms.ALL, new Id(scheme, s"$id:$digest")))) + + // this client won't have access to the root, but the chroot already exists + val chrootClient = KafkaZkClient(zkConnect + chroot, zkAclsEnabled.getOrElse(JaasUtils.isZkSaslEnabled), zkSessionTimeout, + zkConnectionTimeout, zkMaxInFlightRequests, Time.SYSTEM, createChrootIfNecessary = true) + chrootClient.close() + } + @Test def testSetAndGetConsumerOffset(): Unit = { val offset = 123L diff --git a/docs/security.html b/docs/security.html index b90fbbb4ba904..8ff9e6d8b6746 100644 --- a/docs/security.html +++ b/docs/security.html @@ -20,12 +20,12 @@

  • Authentication of connections to brokers from clients (producers and consumers), other brokers and tools, using either SSL or SASL. Kafka supports the following SASL mechanisms: -
      -
    • SASL/GSSAPI (Kerberos) - starting at version 0.9.0.0
    • -
    • SASL/PLAIN - starting at version 0.10.0.0
    • -
    • SASL/SCRAM-SHA-256 and SASL/SCRAM-SHA-512 - starting at version 0.10.2.0
    • -
    • SASL/OAUTHBEARER - starting at version 2.0
    • -
  • +
      +
    • SASL/GSSAPI (Kerberos) - starting at version 0.9.0.0
    • +
    • SASL/PLAIN - starting at version 0.10.0.0
    • +
    • SASL/SCRAM-SHA-256 and SASL/SCRAM-SHA-512 - starting at version 0.10.2.0
    • +
    • SASL/OAUTHBEARER - starting at version 2.0
    • +
  • Authentication of connections from brokers to ZooKeeper
  • Encryption of data transferred between brokers and clients, between brokers, or between brokers and tools using SSL (Note that there is a performance degradation when SSL is enabled, the magnitude of which depends on the CPU type and the JVM implementation.)
  • Authorization of read / write operations by clients
  • @@ -47,7 +47,7 @@

    keytool -keystore {keystorefile} -alias localhost -validity {validity} -genkey -keyalg RSA -storetype pkcs12 +
    > keytool -keystore {keystorefile} -alias localhost -validity {validity} -genkey -keyalg RSA -storetype pkcs12
    You need to specify two parameters in the above command:
    1. keystorefile: the keystore file that stores the keys (and later the certificate) for this broker. The keystore file contains the private @@ -63,7 +63,7 @@

      To generate certificate signing requests run the following command for all server keystores created so far. -
      keytool -keystore server.keystore.jks -alias localhost -validity {validity} -genkey -keyalg RSA -destkeystoretype pkcs12 -ext SAN=DNS:{FQDN},IP:{IPADDRESS1}
      +
      > keytool -keystore server.keystore.jks -alias localhost -validity {validity} -genkey -keyalg RSA -destkeystoretype pkcs12 -ext SAN=DNS:{FQDN},IP:{IPADDRESS1}
      This command assumes that you want to add hostname information to the certificate, if this is not the case, you can omit the extension parameter -ext SAN=DNS:{FQDN},IP:{IPADDRESS1}. Please see below for more information on this.
      Host Name Verification
      @@ -76,7 +76,7 @@
      Host Name Verification
      Server host name verification may be disabled by setting ssl.endpoint.identification.algorithm to an empty string.
      For dynamically configured broker listeners, hostname verification may be disabled using kafka-configs.sh:
      -
      bin/kafka-configs.sh --bootstrap-server localhost:9093 --entity-type brokers --entity-name 0 --alter --add-config "listener.name.internal.ssl.endpoint.identification.algorithm="
      +
      > bin/kafka-configs.sh --bootstrap-server localhost:9093 --entity-type brokers --entity-name 0 --alter --add-config "listener.name.internal.ssl.endpoint.identification.algorithm="

      Note:

      Normally there is no good reason to disable hostname verification apart from being the quickest way to "just get it to work" followed @@ -98,8 +98,8 @@
      Host Name Verification
      signing request. It can also be specified when generating the keypair, but this will not automatically be copied into the signing request.
      - To add a SAN field append the following argument -ext SAN=DNS:{FQDN},IP:{IPADDRESS} to the keytool command: -
      keytool -keystore server.keystore.jks -alias localhost -validity {validity} -genkey -keyalg RSA -destkeystoretype pkcs12 -ext SAN=DNS:{FQDN},IP:{IPADDRESS1}
      + To add a SAN field append the following argument -ext SAN=DNS:{FQDN},IP:{IPADDRESS} to the keytool command: +
      > keytool -keystore server.keystore.jks -alias localhost -validity {validity} -genkey -keyalg RSA -destkeystoretype pkcs12 -ext SAN=DNS:{FQDN},IP:{IPADDRESS1}

    2. Creating your own CA

      @@ -208,25 +208,25 @@
      Host Name Verification
      Then create a database and serial number file, these will be used to keep track of which certificates were signed with this CA. Both of these are simply text files that reside in the same directory as your CA keys. -
      echo 01 > serial.txt
      -touch index.txt
      +
      > echo 01 > serial.txt
      +> touch index.txt
      With these steps done you are now ready to generate your CA that will be used to sign certificates later. -
      openssl req -x509 -config openssl-ca.cnf -newkey rsa:4096 -sha256 -nodes -out cacert.pem -outform PEM
      +
      > openssl req -x509 -config openssl-ca.cnf -newkey rsa:4096 -sha256 -nodes -out cacert.pem -outform PEM
      The CA is simply a public/private key pair and certificate that is signed by itself, and is only intended to sign other certificates.
      This keypair should be kept very safe, if someone gains access to it, they can create and sign certificates that will be trusted by your infrastructure, which means they will be able to impersonate anybody when connecting to any service that trusts this CA.
      The next step is to add the generated CA to the **clients' truststore** so that the clients can trust this CA: -
      keytool -keystore client.truststore.jks -alias CARoot -import -file ca-cert
      +
      > keytool -keystore client.truststore.jks -alias CARoot -import -file ca-cert
      Note: If you configure the Kafka brokers to require client authentication by setting ssl.client.auth to be "requested" or "required" in the Kafka brokers config then you must provide a truststore for the Kafka brokers as well and it should have all the CA certificates that clients' keys were signed by. -
      keytool -keystore server.truststore.jks -alias CARoot -import -file ca-cert
      +
      > keytool -keystore server.truststore.jks -alias CARoot -import -file ca-cert
      In contrast to the keystore in step 1 that stores each machine's own identity, the truststore of a client stores all the certificates that the client should trust. Importing a certificate into one's truststore also means trusting all certificates that are signed by that @@ -237,11 +237,11 @@
      Host Name Verification
    3. Signing the certificate

      Then sign it with the CA: -
      openssl ca -config openssl-ca.cnf -policy signing_policy -extensions signing_req -out {server certificate} -infiles {certificate signing request}
      +
      > openssl ca -config openssl-ca.cnf -policy signing_policy -extensions signing_req -out {server certificate} -infiles {certificate signing request}
      Finally, you need to import both the certificate of the CA and the signed certificate into the keystore: -
      keytool -keystore {keystore} -alias CARoot -import -file {CA certificate}
      -keytool -keystore {keystore} -alias localhost -import -file cert-signed
      +
      > keytool -keystore {keystore} -alias CARoot -import -file {CA certificate}
      +> keytool -keystore {keystore} -alias localhost -import -file cert-signed
      The definitions of the parameters are the following:
        @@ -310,14 +310,14 @@
        SSL key and certificates in PEM format
        harder for a malicious party to obtain certificates with potentially misleading or fraudulent values. It is adviseable to double check signed certificates, whether these contain all requested SAN fields to enable proper hostname verification. The following command can be used to print certificate details to the console, which should be compared with what was originally requested: -
        openssl x509 -in certificate.crt -text -noout
        +
        > openssl x509 -in certificate.crt -text -noout
    4. Configuring Kafka Brokers

      Kafka Brokers support listening for connections on multiple ports. We need to configure the following property in server.properties, which must have one or more comma-separated values: -
      listeners
      +
      listeners
      If SSL is not enabled for inter-broker communication (see below for how to enable it), both PLAINTEXT and SSL ports will be necessary.
      listeners=PLAINTEXT://host.name:port,SSL://host.name:port
      @@ -360,7 +360,7 @@
      SSL key and certificates in PEM format
      with addresses: PLAINTEXT -> EndPoint(192.168.64.1,9092,PLAINTEXT),SSL -> EndPoint(192.168.64.1,9093,SSL)
      To check quickly if the server keystore and truststore are setup properly you can run the following command -
      openssl s_client -debug -connect localhost:9093 -tls1
      (Note: TLSv1 should be listed under ssl.enabled.protocols)
      +
      > openssl s_client -debug -connect localhost:9093 -tls1
      (Note: TLSv1 should be listed under ssl.enabled.protocols)
      In the output of this command you should see server's certificate:
      -----BEGIN CERTIFICATE-----
       {variable sized random bytes}
      @@ -384,56 +384,56 @@ 
      SSL key and certificates in PEM format
      ssl.key.password=test1234
      Other configuration settings that may also be needed depending on our requirements and the broker configuration: -
        -
      1. ssl.provider (Optional). The name of the security provider used for SSL connections. Default value is the default security provider of the JVM.
      2. -
      3. ssl.cipher.suites (Optional). A cipher suite is a named combination of authentication, encryption, MAC and key exchange algorithm used to negotiate the security settings for a network connection using TLS or SSL network protocol.
      4. -
      5. ssl.enabled.protocols=TLSv1.2,TLSv1.1,TLSv1. It should list at least one of the protocols configured on the broker side
      6. -
      7. ssl.truststore.type=JKS
      8. -
      9. ssl.keystore.type=JKS
      10. -
      -
      +
        +
      1. ssl.provider (Optional). The name of the security provider used for SSL connections. Default value is the default security provider of the JVM.
      2. +
      3. ssl.cipher.suites (Optional). A cipher suite is a named combination of authentication, encryption, MAC and key exchange algorithm used to negotiate the security settings for a network connection using TLS or SSL network protocol.
      4. +
      5. ssl.enabled.protocols=TLSv1.2,TLSv1.1,TLSv1. It should list at least one of the protocols configured on the broker side
      6. +
      7. ssl.truststore.type=JKS
      8. +
      9. ssl.keystore.type=JKS
      10. +
      +
      Examples using console-producer and console-consumer: -
      kafka-console-producer.sh --bootstrap-server localhost:9093 --topic test --producer.config client-ssl.properties
      -kafka-console-consumer.sh --bootstrap-server localhost:9093 --topic test --consumer.config client-ssl.properties
      +
      > kafka-console-producer.sh --bootstrap-server localhost:9093 --topic test --producer.config client-ssl.properties
      +> kafka-console-consumer.sh --bootstrap-server localhost:9093 --topic test --consumer.config client-ssl.properties

    7.3 Authentication using SASL

      -
    1. JAAS configuration

      -

      Kafka uses the Java Authentication and Authorization Service - (JAAS) - for SASL configuration.

      -
        -
      1. JAAS configuration for Kafka brokers
        - -

        KafkaServer is the section name in the JAAS file used by each - KafkaServer/Broker. This section provides SASL configuration options - for the broker including any SASL client connections made by the broker - for inter-broker communication. If multiple listeners are configured to use - SASL, the section name may be prefixed with the listener name in lower-case - followed by a period, e.g. sasl_ssl.KafkaServer.

        - -

        Client section is used to authenticate a SASL connection with - zookeeper. It also allows the brokers to set SASL ACL on zookeeper - nodes which locks these nodes down so that only the brokers can - modify it. It is necessary to have the same principal name across all - brokers. If you want to use a section name other than Client, set the - system property zookeeper.sasl.clientconfig to the appropriate - name (e.g., -Dzookeeper.sasl.clientconfig=ZkClient).

        - -

        ZooKeeper uses "zookeeper" as the service name by default. If you - want to change this, set the system property - zookeeper.sasl.client.username to the appropriate name - (e.g., -Dzookeeper.sasl.client.username=zk).

        - -

        Brokers may also configure JAAS using the broker configuration property sasl.jaas.config. - The property name must be prefixed with the listener prefix including the SASL mechanism, - i.e. listener.name.{listenerName}.{saslMechanism}.sasl.jaas.config. Only one - login module may be specified in the config value. If multiple mechanisms are configured on a - listener, configs must be provided for each mechanism using the listener and mechanism prefix. - For example, +

      2. JAAS configuration

        +

        Kafka uses the Java Authentication and Authorization Service + (JAAS) + for SASL configuration.

        +
          +
        1. JAAS configuration for Kafka brokers
          + +

          KafkaServer is the section name in the JAAS file used by each + KafkaServer/Broker. This section provides SASL configuration options + for the broker including any SASL client connections made by the broker + for inter-broker communication. If multiple listeners are configured to use + SASL, the section name may be prefixed with the listener name in lower-case + followed by a period, e.g. sasl_ssl.KafkaServer.

          + +

          Client section is used to authenticate a SASL connection with + zookeeper. It also allows the brokers to set SASL ACL on zookeeper + nodes which locks these nodes down so that only the brokers can + modify it. It is necessary to have the same principal name across all + brokers. If you want to use a section name other than Client, set the + system property zookeeper.sasl.clientconfig to the appropriate + name (e.g., -Dzookeeper.sasl.clientconfig=ZkClient).

          + +

          ZooKeeper uses "zookeeper" as the service name by default. If you + want to change this, set the system property + zookeeper.sasl.client.username to the appropriate name + (e.g., -Dzookeeper.sasl.client.username=zk).

          + +

          Brokers may also configure JAAS using the broker configuration property sasl.jaas.config. + The property name must be prefixed with the listener prefix including the SASL mechanism, + i.e. listener.name.{listenerName}.{saslMechanism}.sasl.jaas.config. Only one + login module may be specified in the config value. If multiple mechanisms are configured on a + listener, configs must be provided for each mechanism using the listener and mechanism prefix. + For example,

          listener.name.sasl_ssl.scram-sha-256.sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
               username="admin" \
               password="admin-secret";
          @@ -443,134 +443,133 @@ 

          - If JAAS configuration is defined at different levels, the order of precedence used is: -
            -
          • Broker configuration property listener.name.{listenerName}.{saslMechanism}.sasl.jaas.config
          • -
          • {listenerName}.KafkaServer section of static JAAS configuration
          • -
          • KafkaServer section of static JAAS configuration
          • -
          - Note that ZooKeeper JAAS config may only be configured using static JAAS configuration. - -

          See GSSAPI (Kerberos), - PLAIN, - SCRAM or - OAUTHBEARER for example broker configurations.

        2. - - -
        3. JAAS configuration for Kafka clients
          - -

          Clients may configure JAAS using the client configuration property - sasl.jaas.config - or using the static JAAS config file - similar to brokers.

          - -
            -
          1. JAAS configuration using client configuration property
            -

            Clients may specify JAAS configuration as a producer or consumer property without - creating a physical configuration file. This mode also enables different producers - and consumers within the same JVM to use different credentials by specifying - different properties for each client. If both static JAAS configuration system property - java.security.auth.login.config and client property sasl.jaas.config - are specified, the client property will be used.

            - -

            See GSSAPI (Kerberos), - PLAIN, - SCRAM or - OAUTHBEARER for example configurations.

          2. - -
          3. JAAS configuration using static config file
            - To configure SASL authentication on the clients using static JAAS config file: -
              -
            1. Add a JAAS config file with a client login section named KafkaClient. Configure - a login module in KafkaClient for the selected mechanism as described in the examples - for setting up GSSAPI (Kerberos), - PLAIN, - SCRAM or - OAUTHBEARER. - For example, GSSAPI - credentials may be configured as: -
              KafkaClient {
              +                    If JAAS configuration is defined at different levels, the order of precedence used is:
              +                    
                +
              • Broker configuration property listener.name.{listenerName}.{saslMechanism}.sasl.jaas.config
              • +
              • {listenerName}.KafkaServer section of static JAAS configuration
              • +
              • KafkaServer section of static JAAS configuration
              • +
              + Note that ZooKeeper JAAS config may only be configured using static JAAS configuration. + +

              See GSSAPI (Kerberos), + PLAIN, + SCRAM or + OAUTHBEARER for example broker configurations.

            2. + +
            3. JAAS configuration for Kafka clients
              + +

              Clients may configure JAAS using the client configuration property + sasl.jaas.config + or using the static JAAS config file + similar to brokers.

              + +
                +
              1. JAAS configuration using client configuration property
                +

                Clients may specify JAAS configuration as a producer or consumer property without + creating a physical configuration file. This mode also enables different producers + and consumers within the same JVM to use different credentials by specifying + different properties for each client. If both static JAAS configuration system property + java.security.auth.login.config and client property sasl.jaas.config + are specified, the client property will be used.

                + +

                See GSSAPI (Kerberos), + PLAIN, + SCRAM or + OAUTHBEARER for example configurations.

              2. + +
              3. JAAS configuration using static config file
                + To configure SASL authentication on the clients using static JAAS config file: +
                  +
                1. Add a JAAS config file with a client login section named KafkaClient. Configure + a login module in KafkaClient for the selected mechanism as described in the examples + for setting up GSSAPI (Kerberos), + PLAIN, + SCRAM or + OAUTHBEARER. + For example, GSSAPI + credentials may be configured as: +
                  KafkaClient {
                       com.sun.security.auth.module.Krb5LoginModule required
                       useKeyTab=true
                       storeKey=true
                       keyTab="/etc/security/keytabs/kafka_client.keytab"
                       principal="kafka-client-1@EXAMPLE.COM";
                   };
                  -
                2. -
                3. Pass the JAAS config file location as JVM parameter to each client JVM. For example: -
                      -Djava.security.auth.login.config=/etc/kafka/kafka_client_jaas.conf
                4. -
                +
              4. +
              5. Pass the JAAS config file location as JVM parameter to each client JVM. For example: +
                -Djava.security.auth.login.config=/etc/kafka/kafka_client_jaas.conf
              6. +
              +
            4. +
          -
        4. -
        -
      3. -
      4. SASL configuration

        - -

        SASL may be used with PLAINTEXT or SSL as the transport layer using the - security protocol SASL_PLAINTEXT or SASL_SSL respectively. If SASL_SSL is - used, then SSL must also be configured.

        - -
          -
        1. SASL mechanisms
          - Kafka supports the following SASL mechanisms: -
        2. -
        3. SASL configuration for Kafka brokers
          +
        4. SASL configuration

          + +

          SASL may be used with PLAINTEXT or SSL as the transport layer using the + security protocol SASL_PLAINTEXT or SASL_SSL respectively. If SASL_SSL is + used, then SSL must also be configured.

          +
            -
          1. Configure a SASL port in server.properties, by adding at least one of - SASL_PLAINTEXT or SASL_SSL to the listeners parameter, which - contains one or more comma-separated values: -
            listeners=SASL_PLAINTEXT://host.name:port
            - If you are only configuring a SASL port (or if you want - the Kafka brokers to authenticate each other using SASL) then make sure - you set the same SASL protocol for inter-broker communication: -
            security.inter.broker.protocol=SASL_PLAINTEXT (or SASL_SSL)
          2. -
          3. Select one or more supported mechanisms - to enable in the broker and follow the steps to configure SASL for the mechanism. - To enable multiple mechanisms in the broker, follow the steps - here.
          4. +
          5. SASL mechanisms
            + Kafka supports the following SASL mechanisms: + +
          6. +
          7. SASL configuration for Kafka brokers
            +
              +
            1. Configure a SASL port in server.properties, by adding at least one of + SASL_PLAINTEXT or SASL_SSL to the listeners parameter, which + contains one or more comma-separated values: +
              listeners=SASL_PLAINTEXT://host.name:port
              + If you are only configuring a SASL port (or if you want + the Kafka brokers to authenticate each other using SASL) then make sure + you set the same SASL protocol for inter-broker communication: +
              security.inter.broker.protocol=SASL_PLAINTEXT (or SASL_SSL)
            2. +
            3. Select one or more supported mechanisms + to enable in the broker and follow the steps to configure SASL for the mechanism. + To enable multiple mechanisms in the broker, follow the steps + here.
            4. +
            +
          8. +
          9. SASL configuration for Kafka clients
            +

            SASL authentication is only supported for the new Java Kafka producer and + consumer, the older API is not supported.

            + +

            To configure SASL authentication on the clients, select a SASL + mechanism that is enabled in + the broker for client authentication and follow the steps to configure SASL + for the selected mechanism.

        5. -
        6. SASL configuration for Kafka clients
          -

          SASL authentication is only supported for the new Java Kafka producer and - consumer, the older API is not supported.

          - -

          To configure SASL authentication on the clients, select a SASL - mechanism that is enabled in - the broker for client authentication and follow the steps to configure SASL - for the selected mechanism.

        7. -
        -
      5. -
      6. Authentication using SASL/Kerberos

        -
          -
        1. Prerequisites
          -
            -
          1. Kerberos
            - If your organization is already using a Kerberos server (for example, by using Active Directory), there is no need to install a new server just for Kafka. Otherwise you will need to install one, your Linux vendor likely has packages for Kerberos and a short guide on how to install and configure it (Ubuntu, Redhat). Note that if you are using Oracle Java, you will need to download JCE policy files for your Java version and copy them to $JAVA_HOME/jre/lib/security.
          2. -
          3. Create Kerberos Principals
            - If you are using the organization's Kerberos or Active Directory server, ask your Kerberos administrator for a principal for each Kafka broker in your cluster and for every operating system user that will access Kafka with Kerberos authentication (via clients and tools).
            - If you have installed your own Kerberos, you will need to create these principals yourself using the following commands: -
            sudo /usr/sbin/kadmin.local -q 'addprinc -randkey kafka/{hostname}@{REALM}'
            -sudo /usr/sbin/kadmin.local -q "ktadd -k /etc/security/keytabs/{keytabname}.keytab kafka/{hostname}@{REALM}"
          4. -
          5. Make sure all hosts can be reachable using hostnames - it is a Kerberos requirement that all your hosts can be resolved with their FQDNs.
          6. -
          -
        2. Configuring Kafka Brokers
          -
            -
          1. Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example (note that each broker should have its own keytab): -
            KafkaServer {
            +        
          2. Authentication using SASL/Kerberos

            +
              +
            1. Prerequisites
              +
                +
              1. Kerberos
                + If your organization is already using a Kerberos server (for example, by using Active Directory), there is no need to install a new server just for Kafka. Otherwise you will need to install one, your Linux vendor likely has packages for Kerberos and a short guide on how to install and configure it (Ubuntu, Redhat). Note that if you are using Oracle Java, you will need to download JCE policy files for your Java version and copy them to $JAVA_HOME/jre/lib/security.
              2. +
              3. Create Kerberos Principals
                + If you are using the organization's Kerberos or Active Directory server, ask your Kerberos administrator for a principal for each Kafka broker in your cluster and for every operating system user that will access Kafka with Kerberos authentication (via clients and tools).
                + If you have installed your own Kerberos, you will need to create these principals yourself using the following commands: +
                > sudo /usr/sbin/kadmin.local -q 'addprinc -randkey kafka/{hostname}@{REALM}'
                +> sudo /usr/sbin/kadmin.local -q "ktadd -k /etc/security/keytabs/{keytabname}.keytab kafka/{hostname}@{REALM}"
              4. +
              5. Make sure all hosts can be reachable using hostnames - it is a Kerberos requirement that all your hosts can be resolved with their FQDNs.
              6. +
              +
            2. Configuring Kafka Brokers
              +
                +
              1. Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example (note that each broker should have its own keytab): +
                KafkaServer {
                     com.sun.security.auth.module.Krb5LoginModule required
                     useKeyTab=true
                     storeKey=true
                @@ -587,426 +586,426 @@ 

                -
              2. - KafkaServer section in the JAAS file tells the broker which principal to use and the location of the keytab where this principal is stored. It - allows the broker to login using the keytab specified in this section. See notes for more details on Zookeeper SASL configuration. -
              3. Pass the JAAS and optionally the krb5 file locations as JVM parameters to each Kafka broker (see here for more details): -
                -Djava.security.krb5.conf=/etc/kafka/krb5.conf
                +                            KafkaServer section in the JAAS file tells the broker which principal to use and the location of the keytab where this principal is stored. It
                +                            allows the broker to login using the keytab specified in this section. See notes for more details on Zookeeper SASL configuration.
                +                        
              4. +
              5. Pass the JAAS and optionally the krb5 file locations as JVM parameters to each Kafka broker (see here for more details): +
                -Djava.security.krb5.conf=/etc/kafka/krb5.conf
                 -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf
                -
              6. -
              7. Make sure the keytabs configured in the JAAS file are readable by the operating system user who is starting kafka broker.
              8. -
              9. Configure SASL port and SASL mechanisms in server.properties as described here. For example: -
                listeners=SASL_PLAINTEXT://host.name:port
                +                        
              10. +
              11. Make sure the keytabs configured in the JAAS file are readable by the operating system user who is starting kafka broker.
              12. +
              13. Configure SASL port and SASL mechanisms in server.properties as described here. For example: +
                listeners=SASL_PLAINTEXT://host.name:port
                 security.inter.broker.protocol=SASL_PLAINTEXT
                 sasl.mechanism.inter.broker.protocol=GSSAPI
                 sasl.enabled.mechanisms=GSSAPI
                -
              14. We must also configure the service name in server.properties, which should match the principal name of the kafka brokers. In the above example, principal is "kafka/kafka1.hostname.com@EXAMPLE.com", so: -
                sasl.kerberos.service.name=kafka
                - -
            3. -
            4. Configuring Kafka Clients
              - To configure SASL authentication on the clients: -
                -
              1. - Clients (producers, consumers, connect workers, etc) will authenticate to the cluster with their - own principal (usually with the same name as the user running the client), so obtain or create - these principals as needed. Then configure the JAAS configuration property for each client. - Different clients within a JVM may run as different users by specifying different principals. - The property sasl.jaas.config in producer.properties or consumer.properties describes - how clients like producer and consumer can connect to the Kafka Broker. The following is an example - configuration for a client using a keytab (recommended for long-running processes): -
                sasl.jaas.config=com.sun.security.auth.module.Krb5LoginModule required \
                +                            We must also configure the service name in server.properties, which should match the principal name of the kafka brokers. In the above example, principal is "kafka/kafka1.hostname.com@EXAMPLE.com", so:
                +                            
                sasl.kerberos.service.name=kafka
                +
              2. +
            5. +
            6. Configuring Kafka Clients
              + To configure SASL authentication on the clients: +
                +
              1. + Clients (producers, consumers, connect workers, etc) will authenticate to the cluster with their + own principal (usually with the same name as the user running the client), so obtain or create + these principals as needed. Then configure the JAAS configuration property for each client. + Different clients within a JVM may run as different users by specifying different principals. + The property sasl.jaas.config in producer.properties or consumer.properties describes + how clients like producer and consumer can connect to the Kafka Broker. The following is an example + configuration for a client using a keytab (recommended for long-running processes): +
                sasl.jaas.config=com.sun.security.auth.module.Krb5LoginModule required \
                     useKeyTab=true \
                     storeKey=true  \
                     keyTab="/etc/security/keytabs/kafka_client.keytab" \
                     principal="kafka-client-1@EXAMPLE.COM";
                - For command-line utilities like kafka-console-consumer or kafka-console-producer, kinit can be used - along with "useTicketCache=true" as in: -
                sasl.jaas.config=com.sun.security.auth.module.Krb5LoginModule required \
                +                            For command-line utilities like kafka-console-consumer or kafka-console-producer, kinit can be used
                +                            along with "useTicketCache=true" as in:
                +                            
                sasl.jaas.config=com.sun.security.auth.module.Krb5LoginModule required \
                     useTicketCache=true;
                - JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers - as described here. Clients use the login section named - KafkaClient. This option allows only one user for all client connections from a JVM.
              2. -
              3. Make sure the keytabs configured in the JAAS configuration are readable by the operating system user who is starting kafka client.
              4. -
              5. Optionally pass the krb5 file locations as JVM parameters to each client JVM (see here for more details): -
                -Djava.security.krb5.conf=/etc/kafka/krb5.conf
              6. -
              7. Configure the following properties in producer.properties or consumer.properties: -
                security.protocol=SASL_PLAINTEXT (or SASL_SSL)
                +                            JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers
                +                            as described here. Clients use the login section named
                +                            KafkaClient. This option allows only one user for all client connections from a JVM.
              8. +
              9. Make sure the keytabs configured in the JAAS configuration are readable by the operating system user who is starting kafka client.
              10. +
              11. Optionally pass the krb5 file locations as JVM parameters to each client JVM (see here for more details): +
                -Djava.security.krb5.conf=/etc/kafka/krb5.conf
              12. +
              13. Configure the following properties in producer.properties or consumer.properties: +
                security.protocol=SASL_PLAINTEXT (or SASL_SSL)
                 sasl.mechanism=GSSAPI
                 sasl.kerberos.service.name=kafka
              14. +
              +
          3. -
          -
        3. - -
        4. Authentication using SASL/PLAIN

          -

          SASL/PLAIN is a simple username/password authentication mechanism that is typically used with TLS for encryption to implement secure authentication. - Kafka supports a default implementation for SASL/PLAIN which can be extended for production use as described here.

          - The username is used as the authenticated Principal for configuration of ACLs etc. -
            -
          1. Configuring Kafka Brokers
            + +
          2. Authentication using SASL/PLAIN

            +

            SASL/PLAIN is a simple username/password authentication mechanism that is typically used with TLS for encryption to implement secure authentication. + Kafka supports a default implementation for SASL/PLAIN which can be extended for production use as described here.

            + The username is used as the authenticated Principal for configuration of ACLs etc.
              -
            1. Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example: -
              KafkaServer {
              +                
            2. Configuring Kafka Brokers
              +
                +
              1. Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example: +
                KafkaServer {
                     org.apache.kafka.common.security.plain.PlainLoginModule required
                     username="admin"
                     password="admin-secret"
                     user_admin="admin-secret"
                     user_alice="alice-secret";
                 };
                - This configuration defines two users (admin and alice). The properties username and password - in the KafkaServer section are used by the broker to initiate connections to other brokers. In this example, - admin is the user for inter-broker communication. The set of properties user_userName defines - the passwords for all users that connect to the broker and the broker validates all client connections including - those from other brokers using these properties.
              2. -
              3. Pass the JAAS config file location as JVM parameter to each Kafka broker: -
                -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf
              4. -
              5. Configure SASL port and SASL mechanisms in server.properties as described here. For example: -
                listeners=SASL_SSL://host.name:port
                +                            This configuration defines two users (admin and alice). The properties username and password
                +                            in the KafkaServer section are used by the broker to initiate connections to other brokers. In this example,
                +                            admin is the user for inter-broker communication. The set of properties user_userName defines
                +                            the passwords for all users that connect to the broker and the broker validates all client connections including
                +                            those from other brokers using these properties.
              6. +
              7. Pass the JAAS config file location as JVM parameter to each Kafka broker: +
                -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf
              8. +
              9. Configure SASL port and SASL mechanisms in server.properties as described here. For example: +
                listeners=SASL_SSL://host.name:port
                 security.inter.broker.protocol=SASL_SSL
                 sasl.mechanism.inter.broker.protocol=PLAIN
                 sasl.enabled.mechanisms=PLAIN
              10. -
              -
            3. +
            +
          3. -
          4. Configuring Kafka Clients
            - To configure SASL authentication on the clients: -
              -
            1. Configure the JAAS configuration property for each client in producer.properties or consumer.properties. - The login module describes how the clients like producer and consumer can connect to the Kafka Broker. - The following is an example configuration for a client for the PLAIN mechanism: -
              sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
              +                
            2. Configuring Kafka Clients
              + To configure SASL authentication on the clients: +
                +
              1. Configure the JAAS configuration property for each client in producer.properties or consumer.properties. + The login module describes how the clients like producer and consumer can connect to the Kafka Broker. + The following is an example configuration for a client for the PLAIN mechanism: +
                sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
                     username="alice" \
                     password="alice-secret";
                -

                The options username and password are used by clients to configure - the user for client connections. In this example, clients connect to the broker as user alice. - Different clients within a JVM may connect as different users by specifying different user names - and passwords in sasl.jaas.config.

                - -

                JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers - as described here. Clients use the login section named - KafkaClient. This option allows only one user for all client connections from a JVM.

              2. -
              3. Configure the following properties in producer.properties or consumer.properties: -
                security.protocol=SASL_SSL
                +                            

                The options username and password are used by clients to configure + the user for client connections. In this example, clients connect to the broker as user alice. + Different clients within a JVM may connect as different users by specifying different user names + and passwords in sasl.jaas.config.

                + +

                JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers + as described here. Clients use the login section named + KafkaClient. This option allows only one user for all client connections from a JVM.

              4. +
              5. Configure the following properties in producer.properties or consumer.properties: +
                security.protocol=SASL_SSL
                 sasl.mechanism=PLAIN
              6. +
              +
            3. +
            4. Use of SASL/PLAIN in production
              +
                +
              • SASL/PLAIN should be used only with SSL as transport layer to ensure that clear passwords are not transmitted on the wire without encryption.
              • +
              • The default implementation of SASL/PLAIN in Kafka specifies usernames and passwords in the JAAS configuration file as shown + here. From Kafka version 2.0 onwards, you can avoid storing clear passwords on disk + by configuring your own callback handlers that obtain username and password from an external source using the configuration options + sasl.server.callback.handler.class and sasl.client.callback.handler.class.
              • +
              • In production systems, external authentication servers may implement password authentication. From Kafka version 2.0 onwards, + you can plug in your own callback handlers that use external authentication servers for password verification by configuring + sasl.server.callback.handler.class.
              • +
              +
          5. -
          6. Use of SASL/PLAIN in production
            -
              -
            • SASL/PLAIN should be used only with SSL as transport layer to ensure that clear passwords are not transmitted on the wire without encryption.
            • -
            • The default implementation of SASL/PLAIN in Kafka specifies usernames and passwords in the JAAS configuration file as shown - here. From Kafka version 2.0 onwards, you can avoid storing clear passwords on disk - by configuring your own callback handlers that obtain username and password from an external source using the configuration options - sasl.server.callback.handler.class and sasl.client.callback.handler.class.
            • -
            • In production systems, external authentication servers may implement password authentication. From Kafka version 2.0 onwards, - you can plug in your own callback handlers that use external authentication servers for password verification by configuring - sasl.server.callback.handler.class.
            • -
            -
          7. -
          -
        5. - -
        6. Authentication using SASL/SCRAM

          -

          Salted Challenge Response Authentication Mechanism (SCRAM) is a family of SASL mechanisms that - addresses the security concerns with traditional mechanisms that perform username/password authentication - like PLAIN and DIGEST-MD5. The mechanism is defined in RFC 5802. - Kafka supports SCRAM-SHA-256 and SCRAM-SHA-512 which - can be used with TLS to perform secure authentication. The username is used as the authenticated - Principal for configuration of ACLs etc. The default SCRAM implementation in Kafka - stores SCRAM credentials in Zookeeper and is suitable for use in Kafka installations where Zookeeper - is on a private network. Refer to Security Considerations - for more details.

          -
            -
          1. Creating SCRAM Credentials
            -

            The SCRAM implementation in Kafka uses Zookeeper as credential store. Credentials can be created in - Zookeeper using kafka-configs.sh. For each SCRAM mechanism enabled, credentials must be created - by adding a config with the mechanism name. Credentials for inter-broker communication must be created - before Kafka brokers are started. Client credentials may be created and updated dynamically and updated - credentials will be used to authenticate new connections.

            -

            Create SCRAM credentials for user alice with password alice-secret: -

            > bin/kafka-configs.sh --zookeeper localhost:2182 --zk-tls-config-file zk_tls_config.properties --alter --add-config 'SCRAM-SHA-256=[iterations=8192,password=alice-secret],SCRAM-SHA-512=[password=alice-secret]' --entity-type users --entity-name alice
            -

            The default iteration count of 4096 is used if iterations are not specified. A random salt is created - and the SCRAM identity consisting of salt, iterations, StoredKey and ServerKey are stored in Zookeeper. - See RFC 5802 for details on SCRAM identity and the individual fields. -

            The following examples also require a user admin for inter-broker communication which can be created using: -

            > bin/kafka-configs.sh --zookeeper localhost:2182 --zk-tls-config-file zk_tls_config.properties --alter --add-config 'SCRAM-SHA-256=[password=admin-secret],SCRAM-SHA-512=[password=admin-secret]' --entity-type users --entity-name admin
            -

            Existing credentials may be listed using the --describe option: -

            > bin/kafka-configs.sh --zookeeper localhost:2182 --zk-tls-config-file zk_tls_config.properties --describe --entity-type users --entity-name alice
            -

            Credentials may be deleted for one or more SCRAM mechanisms using the --alter --delete-config option: -

            > bin/kafka-configs.sh --zookeeper localhost:2182 --zk-tls-config-file zk_tls_config.properties --alter --delete-config 'SCRAM-SHA-512' --entity-type users --entity-name alice
            -
          2. -
          3. Configuring Kafka Brokers
            + +
          4. Authentication using SASL/SCRAM

            +

            Salted Challenge Response Authentication Mechanism (SCRAM) is a family of SASL mechanisms that + addresses the security concerns with traditional mechanisms that perform username/password authentication + like PLAIN and DIGEST-MD5. The mechanism is defined in RFC 5802. + Kafka supports SCRAM-SHA-256 and SCRAM-SHA-512 which + can be used with TLS to perform secure authentication. The username is used as the authenticated + Principal for configuration of ACLs etc. The default SCRAM implementation in Kafka + stores SCRAM credentials in Zookeeper and is suitable for use in Kafka installations where Zookeeper + is on a private network. Refer to Security Considerations + for more details.

              -
            1. Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example: -
              KafkaServer {
              +                
            2. Creating SCRAM Credentials
              +

              The SCRAM implementation in Kafka uses Zookeeper as credential store. Credentials can be created in + Zookeeper using kafka-configs.sh. For each SCRAM mechanism enabled, credentials must be created + by adding a config with the mechanism name. Credentials for inter-broker communication must be created + before Kafka brokers are started. Client credentials may be created and updated dynamically and updated + credentials will be used to authenticate new connections.

              +

              Create SCRAM credentials for user alice with password alice-secret: +

              > bin/kafka-configs.sh --zookeeper localhost:2182 --zk-tls-config-file zk_tls_config.properties --alter --add-config 'SCRAM-SHA-256=[iterations=8192,password=alice-secret],SCRAM-SHA-512=[password=alice-secret]' --entity-type users --entity-name alice
              +

              The default iteration count of 4096 is used if iterations are not specified. A random salt is created + and the SCRAM identity consisting of salt, iterations, StoredKey and ServerKey are stored in Zookeeper. + See RFC 5802 for details on SCRAM identity and the individual fields. +

              The following examples also require a user admin for inter-broker communication which can be created using: +

              > bin/kafka-configs.sh --zookeeper localhost:2182 --zk-tls-config-file zk_tls_config.properties --alter --add-config 'SCRAM-SHA-256=[password=admin-secret],SCRAM-SHA-512=[password=admin-secret]' --entity-type users --entity-name admin
              +

              Existing credentials may be listed using the --describe option: +

              > bin/kafka-configs.sh --zookeeper localhost:2182 --zk-tls-config-file zk_tls_config.properties --describe --entity-type users --entity-name alice
              +

              Credentials may be deleted for one or more SCRAM mechanisms using the --alter --delete-config option: +

              > bin/kafka-configs.sh --zookeeper localhost:2182 --zk-tls-config-file zk_tls_config.properties --alter --delete-config 'SCRAM-SHA-512' --entity-type users --entity-name alice
              +
            3. +
            4. Configuring Kafka Brokers
              +
                +
              1. Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example: +
                KafkaServer {
                     org.apache.kafka.common.security.scram.ScramLoginModule required
                     username="admin"
                     password="admin-secret";
                 };
                - The properties username and password in the KafkaServer section are used by - the broker to initiate connections to other brokers. In this example, admin is the user for - inter-broker communication.
              2. -
              3. Pass the JAAS config file location as JVM parameter to each Kafka broker: -
                -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf
              4. -
              5. Configure SASL port and SASL mechanisms in server.properties as described here.
            5. For example: -
              listeners=SASL_SSL://host.name:port
              +                            The properties username and password in the KafkaServer section are used by
              +                            the broker to initiate connections to other brokers. In this example, admin is the user for
              +                            inter-broker communication.
            6. +
            7. Pass the JAAS config file location as JVM parameter to each Kafka broker: +
              -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf
            8. +
            9. Configure SASL port and SASL mechanisms in server.properties as described here. For example: +
              listeners=SASL_SSL://host.name:port
               security.inter.broker.protocol=SASL_SSL
               sasl.mechanism.inter.broker.protocol=SCRAM-SHA-256 (or SCRAM-SHA-512)
               sasl.enabled.mechanisms=SCRAM-SHA-256 (or SCRAM-SHA-512)
            10. -
            -
          5. +
          +
        7. -
        8. Configuring Kafka Clients
          - To configure SASL authentication on the clients: -
            -
          1. Configure the JAAS configuration property for each client in producer.properties or consumer.properties. - The login module describes how the clients like producer and consumer can connect to the Kafka Broker. - The following is an example configuration for a client for the SCRAM mechanisms: -
            sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
            +                
          2. Configuring Kafka Clients
            + To configure SASL authentication on the clients: +
              +
            1. Configure the JAAS configuration property for each client in producer.properties or consumer.properties. + The login module describes how the clients like producer and consumer can connect to the Kafka Broker. + The following is an example configuration for a client for the SCRAM mechanisms: +
              sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
                   username="alice" \
                   password="alice-secret";
              -

              The options username and password are used by clients to configure - the user for client connections. In this example, clients connect to the broker as user alice. - Different clients within a JVM may connect as different users by specifying different user names - and passwords in sasl.jaas.config.

              +

              The options username and password are used by clients to configure + the user for client connections. In this example, clients connect to the broker as user alice. + Different clients within a JVM may connect as different users by specifying different user names + and passwords in sasl.jaas.config.

              -

              JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers - as described here. Clients use the login section named - KafkaClient. This option allows only one user for all client connections from a JVM.

            2. -
            3. Configure the following properties in producer.properties or consumer.properties: -
              security.protocol=SASL_SSL
              +                            

              JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers + as described here. Clients use the login section named + KafkaClient. This option allows only one user for all client connections from a JVM.

            4. +
            5. Configure the following properties in producer.properties or consumer.properties: +
              security.protocol=SASL_SSL
               sasl.mechanism=SCRAM-SHA-256 (or SCRAM-SHA-512)
            6. +
            +
          3. +
          4. Security Considerations for SASL/SCRAM
            +
              +
            • The default implementation of SASL/SCRAM in Kafka stores SCRAM credentials in Zookeeper. This + is suitable for production use in installations where Zookeeper is secure and on a private network.
            • +
            • Kafka supports only the strong hash functions SHA-256 and SHA-512 with a minimum iteration count + of 4096. Strong hash functions combined with strong passwords and high iteration counts protect + against brute force attacks if Zookeeper security is compromised.
            • +
            • SCRAM should be used only with TLS-encryption to prevent interception of SCRAM exchanges. This + protects against dictionary or brute force attacks and against impersonation if Zookeeper is compromised.
            • +
            • From Kafka version 2.0 onwards, the default SASL/SCRAM credential store may be overridden using custom callback handlers + by configuring sasl.server.callback.handler.class in installations where Zookeeper is not secure.
            • +
            • For more details on security considerations, refer to + RFC 5802. +
            +
        9. -
        10. Security Considerations for SASL/SCRAM
          -
            -
          • The default implementation of SASL/SCRAM in Kafka stores SCRAM credentials in Zookeeper. This - is suitable for production use in installations where Zookeeper is secure and on a private network.
          • -
          • Kafka supports only the strong hash functions SHA-256 and SHA-512 with a minimum iteration count - of 4096. Strong hash functions combined with strong passwords and high iteration counts protect - against brute force attacks if Zookeeper security is compromised.
          • -
          • SCRAM should be used only with TLS-encryption to prevent interception of SCRAM exchanges. This - protects against dictionary or brute force attacks and against impersonation if Zookeeper is compromised.
          • -
          • From Kafka version 2.0 onwards, the default SASL/SCRAM credential store may be overridden using custom callback handlers - by configuring sasl.server.callback.handler.class in installations where Zookeeper is not secure.
          • -
          • For more details on security considerations, refer to - RFC 5802. -
          -
        11. -
        -
      7. - -
      8. Authentication using SASL/OAUTHBEARER

        -

        The OAuth 2 Authorization Framework "enables a third-party application to obtain limited access to an HTTP service, - either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP - service, or by allowing the third-party application to obtain access on its own behalf." The SASL OAUTHBEARER mechanism - enables the use of the framework in a SASL (i.e. a non-HTTP) context; it is defined in RFC 7628. - The default OAUTHBEARER implementation in Kafka creates and validates Unsecured JSON Web Tokens - and is only suitable for use in non-production Kafka installations. Refer to Security Considerations - for more details.

        -
          -
        1. Configuring Kafka Brokers
          + +
        2. Authentication using SASL/OAUTHBEARER

          +

          The OAuth 2 Authorization Framework "enables a third-party application to obtain limited access to an HTTP service, + either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP + service, or by allowing the third-party application to obtain access on its own behalf." The SASL OAUTHBEARER mechanism + enables the use of the framework in a SASL (i.e. a non-HTTP) context; it is defined in RFC 7628. + The default OAUTHBEARER implementation in Kafka creates and validates Unsecured JSON Web Tokens + and is only suitable for use in non-production Kafka installations. Refer to Security Considerations + for more details.

            -
          1. Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example: -
            KafkaServer {
            +                
          2. Configuring Kafka Brokers
            +
              +
            1. Add a suitably modified JAAS file similar to the one below to each Kafka broker's config directory, let's call it kafka_server_jaas.conf for this example: +
              KafkaServer {
                   org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required
                   unsecuredLoginStringClaim_sub="admin";
               };
              - The property unsecuredLoginStringClaim_sub in the KafkaServer section is used by - the broker when it initiates connections to other brokers. In this example, admin will appear in the - subject (sub) claim and will be the user for inter-broker communication.
            2. -
            3. Pass the JAAS config file location as JVM parameter to each Kafka broker: -
              -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf
            4. -
            5. Configure SASL port and SASL mechanisms in server.properties as described here.
          3. For example: -
            listeners=SASL_SSL://host.name:port (or SASL_PLAINTEXT if non-production)
            +                            The property unsecuredLoginStringClaim_sub in the KafkaServer section is used by
            +                            the broker when it initiates connections to other brokers. In this example, admin will appear in the
            +                            subject (sub) claim and will be the user for inter-broker communication.
          4. +
          5. Pass the JAAS config file location as JVM parameter to each Kafka broker: +
            -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf
          6. +
          7. Configure SASL port and SASL mechanisms in server.properties as described here. For example: +
            listeners=SASL_SSL://host.name:port (or SASL_PLAINTEXT if non-production)
             security.inter.broker.protocol=SASL_SSL (or SASL_PLAINTEXT if non-production)
             sasl.mechanism.inter.broker.protocol=OAUTHBEARER
             sasl.enabled.mechanisms=OAUTHBEARER
          8. -
          -
        3. +
        +
      9. -
      10. Configuring Kafka Clients
        - To configure SASL authentication on the clients: -
          -
        1. Configure the JAAS configuration property for each client in producer.properties or consumer.properties. - The login module describes how the clients like producer and consumer can connect to the Kafka Broker. - The following is an example configuration for a client for the OAUTHBEARER mechanisms: -
          sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
          +                
        2. Configuring Kafka Clients
          + To configure SASL authentication on the clients: +
            +
          1. Configure the JAAS configuration property for each client in producer.properties or consumer.properties. + The login module describes how the clients like producer and consumer can connect to the Kafka Broker. + The following is an example configuration for a client for the OAUTHBEARER mechanisms: +
            sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
                 unsecuredLoginStringClaim_sub="alice";
            -

            The option unsecuredLoginStringClaim_sub is used by clients to configure - the subject (sub) claim, which determines the user for client connections. - In this example, clients connect to the broker as user alice. - Different clients within a JVM may connect as different users by specifying different subject (sub) - claims in sasl.jaas.config.

            - -

            JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers - as described here. Clients use the login section named - KafkaClient. This option allows only one user for all client connections from a JVM.

          2. -
          3. Configure the following properties in producer.properties or consumer.properties: -
            security.protocol=SASL_SSL (or SASL_PLAINTEXT if non-production)
            +                            

            The option unsecuredLoginStringClaim_sub is used by clients to configure + the subject (sub) claim, which determines the user for client connections. + In this example, clients connect to the broker as user alice. + Different clients within a JVM may connect as different users by specifying different subject (sub) + claims in sasl.jaas.config.

            + +

            JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers + as described here. Clients use the login section named + KafkaClient. This option allows only one user for all client connections from a JVM.

          4. +
          5. Configure the following properties in producer.properties or consumer.properties: +
            security.protocol=SASL_SSL (or SASL_PLAINTEXT if non-production)
             sasl.mechanism=OAUTHBEARER
          6. -
          7. The default implementation of SASL/OAUTHBEARER depends on the jackson-databind library. - Since it's an optional dependency, users have to configure it as a dependency via their build tool.
          8. +
          9. The default implementation of SASL/OAUTHBEARER depends on the jackson-databind library. + Since it's an optional dependency, users have to configure it as a dependency via their build tool.
          10. +
          +
        3. +
        4. Unsecured Token Creation Options for SASL/OAUTHBEARER
          +
            +
          • The default implementation of SASL/OAUTHBEARER in Kafka creates and validates Unsecured JSON Web Tokens. + While suitable only for non-production use, it does provide the flexibility to create arbitrary tokens in a DEV or TEST environment.
          • +
          • Here are the various supported JAAS module options on the client side (and on the broker side if OAUTHBEARER is the inter-broker protocol): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            JAAS Module Option for Unsecured Token CreationDocumentation
            unsecuredLoginStringClaim_<claimname>="value"Creates a String claim with the given name and value. Any valid + claim name can be specified except 'iat' and 'exp' (these are + automatically generated).
            unsecuredLoginNumberClaim_<claimname>="value"Creates a Number claim with the given name and value. Any valid + claim name can be specified except 'iat' and 'exp' (these are + automatically generated).
            unsecuredLoginListClaim_<claimname>="value"Creates a String List claim with the given name and values parsed + from the given value where the first character is taken as the delimiter. For + example: unsecuredLoginListClaim_fubar="|value1|value2". Any valid + claim name can be specified except 'iat' and 'exp' (these are + automatically generated).
            unsecuredLoginExtension_<extensionname>="value"Creates a String extension with the given name and value. + For example: unsecuredLoginExtension_traceId="123". A valid extension name + is any sequence of lowercase or uppercase alphabet characters. In addition, the "auth" extension name is reserved. + A valid extension value is any combination of characters with ASCII codes 1-127. +
            unsecuredLoginPrincipalClaimNameSet to a custom claim name if you wish the name of the String + claim holding the principal name to be something other than 'sub'.
            unsecuredLoginLifetimeSecondsSet to an integer value if the token expiration is to be set to something + other than the default value of 3600 seconds (which is 1 hour). The + 'exp' claim will be set to reflect the expiration time.
            unsecuredLoginScopeClaimNameSet to a custom claim name if you wish the name of the String or + String List claim holding any token scope to be something other than + 'scope'.
            +
          • +
          +
        5. +
        6. Unsecured Token Validation Options for SASL/OAUTHBEARER
          +
            +
          • Here are the various supported JAAS module options on the broker side for Unsecured JSON Web Token validation: + + + + + + + + + + + + + + + + + + + + + +
            JAAS Module Option for Unsecured Token ValidationDocumentation
            unsecuredValidatorPrincipalClaimName="value"Set to a non-empty value if you wish a particular String claim + holding a principal name to be checked for existence; the default is to check + for the existence of the 'sub' claim.
            unsecuredValidatorScopeClaimName="value"Set to a custom claim name if you wish the name of the String or + String List claim holding any token scope to be something other than + 'scope'.
            unsecuredValidatorRequiredScope="value"Set to a space-delimited list of scope values if you wish the + String/String List claim holding the token scope to be checked to + make sure it contains certain values.
            unsecuredValidatorAllowableClockSkewMs="value"Set to a positive integer value if you wish to allow up to some number of + positive milliseconds of clock skew (the default is 0).
            +
          • +
          • The default unsecured SASL/OAUTHBEARER implementation may be overridden (and must be overridden in production environments) + using custom login and SASL Server callback handlers.
          • +
          • For more details on security considerations, refer to RFC 6749, Section 10.
          • +
          +
        7. +
        8. Token Refresh for SASL/OAUTHBEARER
          + Kafka periodically refreshes any token before it expires so that the client can continue to make + connections to brokers. The parameters that impact how the refresh algorithm + operates are specified as part of the producer/consumer/broker configuration + and are as follows. See the documentation for these properties elsewhere for + details. The default values are usually reasonable, in which case these + configuration parameters would not need to be explicitly set. + + + + + + + + + + + + + + + + +
          Producer/Consumer/Broker Configuration Property
          sasl.login.refresh.window.factor
          sasl.login.refresh.window.jitter
          sasl.login.refresh.min.period.seconds
          sasl.login.refresh.min.buffer.seconds
          +
        9. +
        10. Secure/Production Use of SASL/OAUTHBEARER
          + Production use cases will require writing an implementation of + org.apache.kafka.common.security.auth.AuthenticateCallbackHandler that can handle an instance of + org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback and declaring it via either the + sasl.login.callback.handler.class configuration option for a + non-broker client or via the + listener.name.sasl_ssl.oauthbearer.sasl.login.callback.handler.class + configuration option for brokers (when SASL/OAUTHBEARER is the inter-broker + protocol). +

          + Production use cases will also require writing an implementation of + org.apache.kafka.common.security.auth.AuthenticateCallbackHandler that can handle an instance of + org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallback and declaring it via the + listener.name.sasl_ssl.oauthbearer.sasl.server.callback.handler.class + broker configuration option. +

        11. +
        12. Security Considerations for SASL/OAUTHBEARER
          +
            +
          • The default implementation of SASL/OAUTHBEARER in Kafka creates and validates Unsecured JSON Web Tokens. + This is suitable only for non-production use.
          • +
          • OAUTHBEARER should be used in production enviromnments only with TLS-encryption to prevent interception of tokens.
          • +
          • The default unsecured SASL/OAUTHBEARER implementation may be overridden (and must be overridden in production environments) + using custom login and SASL Server callback handlers as described above.
          • +
          • For more details on OAuth 2 security considerations in general, refer to RFC 6749, Section 10.
          • +
          +
      11. -
      12. Unsecured Token Creation Options for SASL/OAUTHBEARER
        -
          -
        • The default implementation of SASL/OAUTHBEARER in Kafka creates and validates Unsecured JSON Web Tokens. - While suitable only for non-production use, it does provide the flexibility to create arbitrary tokens in a DEV or TEST environment.
        • -
        • Here are the various supported JAAS module options on the client side (and on the broker side if OAUTHBEARER is the inter-broker protocol): - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          JAAS Module Option for Unsecured Token CreationDocumentation
          unsecuredLoginStringClaim_<claimname>="value"Creates a String claim with the given name and value. Any valid - claim name can be specified except 'iat' and 'exp' (these are - automatically generated).
          unsecuredLoginNumberClaim_<claimname>="value"Creates a Number claim with the given name and value. Any valid - claim name can be specified except 'iat' and 'exp' (these are - automatically generated).
          unsecuredLoginListClaim_<claimname>="value"Creates a String List claim with the given name and values parsed - from the given value where the first character is taken as the delimiter. For - example: unsecuredLoginListClaim_fubar="|value1|value2". Any valid - claim name can be specified except 'iat' and 'exp' (these are - automatically generated).
          unsecuredLoginExtension_<extensionname>="value"Creates a String extension with the given name and value. - For example: unsecuredLoginExtension_traceId="123". A valid extension name - is any sequence of lowercase or uppercase alphabet characters. In addition, the "auth" extension name is reserved. - A valid extension value is any combination of characters with ASCII codes 1-127. -
          unsecuredLoginPrincipalClaimNameSet to a custom claim name if you wish the name of the String - claim holding the principal name to be something other than 'sub'.
          unsecuredLoginLifetimeSecondsSet to an integer value if the token expiration is to be set to something - other than the default value of 3600 seconds (which is 1 hour). The - 'exp' claim will be set to reflect the expiration time.
          unsecuredLoginScopeClaimNameSet to a custom claim name if you wish the name of the String or - String List claim holding any token scope to be something other than - 'scope'.
          -
        • -
        -
      13. -
      14. Unsecured Token Validation Options for SASL/OAUTHBEARER
        -
          -
        • Here are the various supported JAAS module options on the broker side for Unsecured JSON Web Token validation: - - - - - - - - - - - - - - - - - - - - - -
          JAAS Module Option for Unsecured Token ValidationDocumentation
          unsecuredValidatorPrincipalClaimName="value"Set to a non-empty value if you wish a particular String claim - holding a principal name to be checked for existence; the default is to check - for the existence of the 'sub' claim.
          unsecuredValidatorScopeClaimName="value"Set to a custom claim name if you wish the name of the String or - String List claim holding any token scope to be something other than - 'scope'.
          unsecuredValidatorRequiredScope="value"Set to a space-delimited list of scope values if you wish the - String/String List claim holding the token scope to be checked to - make sure it contains certain values.
          unsecuredValidatorAllowableClockSkewMs="value"Set to a positive integer value if you wish to allow up to some number of - positive milliseconds of clock skew (the default is 0).
          -
        • -
        • The default unsecured SASL/OAUTHBEARER implementation may be overridden (and must be overridden in production environments) - using custom login and SASL Server callback handlers.
        • -
        • For more details on security considerations, refer to RFC 6749, Section 10.
        • -
        -
      15. -
      16. Token Refresh for SASL/OAUTHBEARER
        - Kafka periodically refreshes any token before it expires so that the client can continue to make - connections to brokers. The parameters that impact how the refresh algorithm - operates are specified as part of the producer/consumer/broker configuration - and are as follows. See the documentation for these properties elsewhere for - details. The default values are usually reasonable, in which case these - configuration parameters would not need to be explicitly set. - - - - - - - - - - - - - - - - -
        Producer/Consumer/Broker Configuration Property
        sasl.login.refresh.window.factor
        sasl.login.refresh.window.jitter
        sasl.login.refresh.min.period.seconds
        sasl.login.refresh.min.buffer.seconds
        -
      17. -
      18. Secure/Production Use of SASL/OAUTHBEARER
        - Production use cases will require writing an implementation of - org.apache.kafka.common.security.auth.AuthenticateCallbackHandler that can handle an instance of - org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback and declaring it via either the - sasl.login.callback.handler.class configuration option for a - non-broker client or via the - listener.name.sasl_ssl.oauthbearer.sasl.login.callback.handler.class - configuration option for brokers (when SASL/OAUTHBEARER is the inter-broker - protocol). -

        - Production use cases will also require writing an implementation of - org.apache.kafka.common.security.auth.AuthenticateCallbackHandler that can handle an instance of - org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallback and declaring it via the - listener.name.sasl_ssl.oauthbearer.sasl.server.callback.handler.class - broker configuration option. -

      19. -
      20. Security Considerations for SASL/OAUTHBEARER
        -
          -
        • The default implementation of SASL/OAUTHBEARER in Kafka creates and validates Unsecured JSON Web Tokens. - This is suitable only for non-production use.
        • -
        • OAUTHBEARER should be used in production enviromnments only with TLS-encryption to prevent interception of tokens.
        • -
        • The default unsecured SASL/OAUTHBEARER implementation may be overridden (and must be overridden in production environments) - using custom login and SASL Server callback handlers as described above.
        • -
        • For more details on OAuth 2 security considerations in general, refer to RFC 6749, Section 10.
        • -
        -
      21. -
      -
    2. -
    3. Enabling multiple SASL mechanisms in a broker

      -
        -
      1. Specify configuration for the login modules of all enabled mechanisms in the KafkaServer section of the JAAS config file. For example: -
        KafkaServer {
        +        
      2. Enabling multiple SASL mechanisms in a broker

        +
          +
        1. Specify configuration for the login modules of all enabled mechanisms in the KafkaServer section of the JAAS config file. For example: +
          KafkaServer {
               com.sun.security.auth.module.Krb5LoginModule required
               useKeyTab=true
               storeKey=true
          @@ -1019,130 +1018,130 @@ 

        2. -
        3. Enable the SASL mechanisms in server.properties:
          sasl.enabled.mechanisms=GSSAPI,PLAIN,SCRAM-SHA-256,SCRAM-SHA-512,OAUTHBEARER
        4. -
        5. Specify the SASL security protocol and mechanism for inter-broker communication in server.properties if required: -
          security.inter.broker.protocol=SASL_PLAINTEXT (or SASL_SSL)
          +                
        6. Enable the SASL mechanisms in server.properties:
          sasl.enabled.mechanisms=GSSAPI,PLAIN,SCRAM-SHA-256,SCRAM-SHA-512,OAUTHBEARER
        7. +
        8. Specify the SASL security protocol and mechanism for inter-broker communication in server.properties if required: +
          security.inter.broker.protocol=SASL_PLAINTEXT (or SASL_SSL)
           sasl.mechanism.inter.broker.protocol=GSSAPI (or one of the other enabled mechanisms)
        9. -
        10. Follow the mechanism-specific steps in GSSAPI (Kerberos), - PLAIN, - SCRAM and OAUTHBEARER - to configure SASL for the enabled mechanisms.
        11. -
        -
      3. -
      4. Modifying SASL mechanism in a Running Cluster

        -

        SASL mechanism can be modified in a running cluster using the following sequence:

        -
          -
        1. Enable new SASL mechanism by adding the mechanism to sasl.enabled.mechanisms in server.properties for each broker. Update JAAS config file to include both - mechanisms as described here. Incrementally bounce the cluster nodes.
        2. -
        3. Restart clients using the new mechanism.
        4. -
        5. To change the mechanism of inter-broker communication (if this is required), set sasl.mechanism.inter.broker.protocol in server.properties to the new mechanism and - incrementally bounce the cluster again.
        6. -
        7. To remove old mechanism (if this is required), remove the old mechanism from sasl.enabled.mechanisms in server.properties and remove the entries for the - old mechanism from JAAS config file. Incrementally bounce the cluster again.
        8. -
        -
      5. - -
      6. Authentication using Delegation Tokens

        -

        Delegation token based authentication is a lightweight authentication mechanism to complement existing SASL/SSL - methods. Delegation tokens are shared secrets between kafka brokers and clients. Delegation tokens will help processing - frameworks to distribute the workload to available workers in a secure environment without the added cost of distributing - Kerberos TGT/keytabs or keystores when 2-way SSL is used. See KIP-48 - for more details.

        - -

        Typical steps for delegation token usage are:

        -
          -
        1. User authenticates with the Kafka cluster via SASL or SSL, and obtains a delegation token. This can be done - using Admin APIs or using kafka-delegation-tokens.sh script.
        2. -
        3. User securely passes the delegation token to Kafka clients for authenticating with the Kafka cluster.
        4. -
        5. Token owner/renewer can renew/expire the delegation tokens.
        6. -
        - -
          -
        1. Token Management
          -

          A secret is used to generate and verify delegation tokens. This is supplied using config - option delegation.token.secret.key. The same secret key must be configured across all the brokers. - If the secret is not set or set to empty string, brokers will disable the delegation token authentication.

          - -

          In the current implementation, token details are stored in Zookeeper and is suitable for use in Kafka installations where - Zookeeper is on a private network. Also currently, this secret is stored as plain text in the server.properties - config file. We intend to make these configurable in a future Kafka release.

          - -

          A token has a current life, and a maximum renewable life. By default, tokens must be renewed once every 24 hours - for up to 7 days. These can be configured using delegation.token.expiry.time.ms - and delegation.token.max.lifetime.ms config options.

          - -

          Tokens can also be cancelled explicitly. If a token is not renewed by the token’s expiration time or if token - is beyond the max life time, it will be deleted from all broker caches as well as from zookeeper.

          +
        2. Follow the mechanism-specific steps in GSSAPI (Kerberos), + PLAIN, + SCRAM and OAUTHBEARER + to configure SASL for the enabled mechanisms.
        3. +
      7. - -
      8. Creating Delegation Tokens
        -

        Tokens can be created by using Admin APIs or using kafka-delegation-tokens.sh script. - Delegation token requests (create/renew/expire/describe) should be issued only on SASL or SSL authenticated channels. - Tokens can not be requests if the initial authentication is done through delegation token. - kafka-delegation-tokens.sh script examples are given below.

        -

        Create a delegation token: -

        > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --create   --max-life-time-period -1 --command-config client.properties --renewer-principal User:user1
        -

        Renew a delegation token: -

        > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --renew    --renew-time-period -1 --command-config client.properties --hmac ABCDEFGHIJK
        -

        Expire a delegation token: -

        > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --expire   --expiry-time-period -1   --command-config client.properties  --hmac ABCDEFGHIJK
        -

        Existing tokens can be described using the --describe option: -

        > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --describe --command-config client.properties  --owner-principal User:user1
        +
      9. Modifying SASL mechanism in a Running Cluster

        +

        SASL mechanism can be modified in a running cluster using the following sequence:

        +
          +
        1. Enable new SASL mechanism by adding the mechanism to sasl.enabled.mechanisms in server.properties for each broker. Update JAAS config file to include both + mechanisms as described here. Incrementally bounce the cluster nodes.
        2. +
        3. Restart clients using the new mechanism.
        4. +
        5. To change the mechanism of inter-broker communication (if this is required), set sasl.mechanism.inter.broker.protocol in server.properties to the new mechanism and + incrementally bounce the cluster again.
        6. +
        7. To remove old mechanism (if this is required), remove the old mechanism from sasl.enabled.mechanisms in server.properties and remove the entries for the + old mechanism from JAAS config file. Incrementally bounce the cluster again.
        8. +
      10. -
      11. Token Authentication
        -

        Delegation token authentication piggybacks on the current SASL/SCRAM authentication mechanism. We must enable - SASL/SCRAM mechanism on Kafka cluster as described in here.

        -

        Configuring Kafka Clients:

        -
          -
        1. Configure the JAAS configuration property for each client in producer.properties or consumer.properties. - The login module describes how the clients like producer and consumer can connect to the Kafka Broker. - The following is an example configuration for a client for the token authentication: -
          sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
          +        
        2. Authentication using Delegation Tokens

          +

          Delegation token based authentication is a lightweight authentication mechanism to complement existing SASL/SSL + methods. Delegation tokens are shared secrets between kafka brokers and clients. Delegation tokens will help processing + frameworks to distribute the workload to available workers in a secure environment without the added cost of distributing + Kerberos TGT/keytabs or keystores when 2-way SSL is used. See KIP-48 + for more details.

          + +

          Typical steps for delegation token usage are:

          +
            +
          1. User authenticates with the Kafka cluster via SASL or SSL, and obtains a delegation token. This can be done + using Admin APIs or using kafka-delegation-tokens.sh script.
          2. +
          3. User securely passes the delegation token to Kafka clients for authenticating with the Kafka cluster.
          4. +
          5. Token owner/renewer can renew/expire the delegation tokens.
          6. +
          + +
            +
          1. Token Management
            +

            A secret is used to generate and verify delegation tokens. This is supplied using config + option delegation.token.secret.key. The same secret key must be configured across all the brokers. + If the secret is not set or set to empty string, brokers will disable the delegation token authentication.

            + +

            In the current implementation, token details are stored in Zookeeper and is suitable for use in Kafka installations where + Zookeeper is on a private network. Also currently, this secret is stored as plain text in the server.properties + config file. We intend to make these configurable in a future Kafka release.

            + +

            A token has a current life, and a maximum renewable life. By default, tokens must be renewed once every 24 hours + for up to 7 days. These can be configured using delegation.token.expiry.time.ms + and delegation.token.max.lifetime.ms config options.

            + +

            Tokens can also be cancelled explicitly. If a token is not renewed by the token’s expiration time or if token + is beyond the max life time, it will be deleted from all broker caches as well as from zookeeper.

            +
          2. + +
          3. Creating Delegation Tokens
            +

            Tokens can be created by using Admin APIs or using kafka-delegation-tokens.sh script. + Delegation token requests (create/renew/expire/describe) should be issued only on SASL or SSL authenticated channels. + Tokens can not be requests if the initial authentication is done through delegation token. + kafka-delegation-tokens.sh script examples are given below.

            +

            Create a delegation token: +

            > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --create   --max-life-time-period -1 --command-config client.properties --renewer-principal User:user1
            +

            Renew a delegation token: +

            > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --renew    --renew-time-period -1 --command-config client.properties --hmac ABCDEFGHIJK
            +

            Expire a delegation token: +

            > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --expire   --expiry-time-period -1   --command-config client.properties  --hmac ABCDEFGHIJK
            +

            Existing tokens can be described using the --describe option: +

            > bin/kafka-delegation-tokens.sh --bootstrap-server localhost:9092 --describe --command-config client.properties  --owner-principal User:user1
            +
          4. +
          5. Token Authentication
            +

            Delegation token authentication piggybacks on the current SASL/SCRAM authentication mechanism. We must enable + SASL/SCRAM mechanism on Kafka cluster as described in here.

            + +

            Configuring Kafka Clients:

            +
              +
            1. Configure the JAAS configuration property for each client in producer.properties or consumer.properties. + The login module describes how the clients like producer and consumer can connect to the Kafka Broker. + The following is an example configuration for a client for the token authentication: +
              sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
                   username="tokenID123" \
                   password="lAYYSFmLs4bTjf+lTZ1LCHR/ZZFNA==" \
                   tokenauth="true";
              -

              The options username and password are used by clients to configure the token id and - token HMAC. And the option tokenauth is used to indicate the server about token authentication. - In this example, clients connect to the broker using token id: tokenID123. Different clients within a - JVM may connect using different tokens by specifying different token details in sasl.jaas.config.

              +

              The options username and password are used by clients to configure the token id and + token HMAC. And the option tokenauth is used to indicate the server about token authentication. + In this example, clients connect to the broker using token id: tokenID123. Different clients within a + JVM may connect using different tokens by specifying different token details in sasl.jaas.config.

              -

              JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers - as described here. Clients use the login section named - KafkaClient. This option allows only one user for all client connections from a JVM.

            2. -
            -
          6. +

            JAAS configuration for clients may alternatively be specified as a JVM parameter similar to brokers + as described here. Clients use the login section named + KafkaClient. This option allows only one user for all client connections from a JVM.

            +
          +
        3. -
        4. Procedure to manually rotate the secret:
          -

          We require a re-deployment when the secret needs to be rotated. During this process, already connected clients - will continue to work. But any new connection requests and renew/expire requests with old tokens can fail. Steps are given below.

          +
        5. Procedure to manually rotate the secret:
          +

          We require a re-deployment when the secret needs to be rotated. During this process, already connected clients + will continue to work. But any new connection requests and renew/expire requests with old tokens can fail. Steps are given below.

          -
            -
          1. Expire all existing tokens.
          2. -
          3. Rotate the secret by rolling upgrade, and
          4. -
          5. Generate new tokens
          6. -
          -

          We intend to automate this in a future Kafka release.

          -
        6. +
            +
          1. Expire all existing tokens.
          2. +
          3. Rotate the secret by rolling upgrade, and
          4. +
          5. Generate new tokens
          6. +
          +

          We intend to automate this in a future Kafka release.

          +
        7. -
        8. Notes on Delegation Tokens
          -
            -
          • Currently, we only allow a user to create delegation token for that user only. Owner/Renewers can renew or expire tokens. - Owner/renewers can always describe their own tokens. To describe others tokens, we need to add DESCRIBE permission on Token Resource.
          • -
          +
        9. Notes on Delegation Tokens
          +
            +
          • Currently, we only allow a user to create delegation token for that user only. Owner/Renewers can renew or expire tokens. + Owner/renewers can always describe their own tokens. To describe others tokens, we need to add DESCRIBE permission on Token Resource.
          • +
          +
        10. +
      12. -
      -

    7.4 Authorization and ACLs

    Kafka ships with a pluggable Authorizer and an out-of-box authorizer implementation that uses zookeeper to store all the acls. The Authorizer is configured by setting authorizer.class.name in server.properties. To enable the out of the box implementation use: -
    authorizer.class.name=kafka.security.authorizer.AclAuthorizer
    +
    authorizer.class.name=kafka.security.authorizer.AclAuthorizer
    Kafka acls are defined in the general format of "Principal P is [Allowed/Denied] Operation O From Host H on any Resource R matching ResourcePattern RP". You can read more about the acl structure in KIP-11 and resource patterns in KIP-290. In order to add, remove or list acls you can use the Kafka authorizer CLI. By default, if no ResourcePatterns match a specific Resource R, then R has no associated acls, and therefore no one other than super users is allowed to access R. If you want to change that behavior, you can include the following in server.properties. -
    allow.everyone.if.no.acl.found=true
    +
    allow.everyone.if.no.acl.found=true
    One can also add super users in server.properties like the following (note that the delimiter is semicolon since SSL user names may contain comma). Default PrincipalType string "User" is case sensitive. -
    super.users=User:Bob;User:Alice
    +
    super.users=User:Bob;User:Alice

    Customizing SSL User Name
    @@ -1166,7 +1165,7 @@
    For advanced use cases, one can customize the name by setting a customized PrincipalBuilder in server.properties like the following. -
    principal.builder.class=CustomizedPrincipalBuilderClass
    +
    principal.builder.class=CustomizedPrincipalBuilderClass
    Customizing SASL User Name
    @@ -1181,7 +1180,7 @@
    < RULE:[n:string](regexp)s/pattern/replacement/g/U An example of adding a rule to properly translate user@MYDOMAIN.COM to user while also keeping the default rule in place is: -
    sasl.kerberos.principal.to.local.rules=RULE:[1:$1@$0](.*@MYDOMAIN.COM)s/@.*//,DEFAULT
    +
    sasl.kerberos.principal.to.local.rules=RULE:[1:$1@$0](.*@MYDOMAIN.COM)s/@.*//,DEFAULT

    Command Line Interface

    Kafka Authorization management CLI can be found under bin directory with all the other CLIs. The CLI script is called kafka-acls.sh. Following lists all the options that the script supports: @@ -1369,55 +1368,55 @@

    Configuration - +

    Examples

    • Adding Acls
      - Suppose you want to add an acl "Principals User:Bob and User:Alice are allowed to perform Operation Read and Write on Topic Test-Topic from IP 198.51.100.0 and IP 198.51.100.1". You can do that by executing the CLI with following options: -
      bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --allow-principal User:Alice --allow-host 198.51.100.0 --allow-host 198.51.100.1 --operation Read --operation Write --topic Test-topic
      + Suppose you want to add an acl "Principals User:Bob and User:Alice are allowed to perform Operation Read and Write on Topic Test-Topic from IP 198.51.100.0 and IP 198.51.100.1". You can do that by executing the CLI with following options: +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --allow-principal User:Alice --allow-host 198.51.100.0 --allow-host 198.51.100.1 --operation Read --operation Write --topic Test-topic
      By default, all principals that don't have an explicit acl that allows access for an operation to a resource are denied. In rare cases where an allow acl is defined that allows access to all but some principal we will have to use the --deny-principal and --deny-host option. For example, if we want to allow all users to Read from Test-topic but only deny User:BadBob from IP 198.51.100.3 we can do so using following commands: -
      bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:* --allow-host * --deny-principal User:BadBob --deny-host 198.51.100.3 --operation Read --topic Test-topic
      +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:* --allow-host * --deny-principal User:BadBob --deny-host 198.51.100.3 --operation Read --topic Test-topic
      Note that --allow-host and --deny-host only support IP addresses (hostnames are not supported). Above examples add acls to a topic by specifying --topic [topic-name] as the resource pattern option. Similarly user can add acls to cluster by specifying --cluster and to a consumer group by specifying --group [group-name]. You can add acls on any resource of a certain type, e.g. suppose you wanted to add an acl "Principal User:Peter is allowed to produce to any Topic from IP 198.51.200.0" You can do that by using the wildcard resource '*', e.g. by executing the CLI with following options: -
      bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Peter --allow-host 198.51.200.1 --producer --topic *
      +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Peter --allow-host 198.51.200.1 --producer --topic *
      You can add acls on prefixed resource patterns, e.g. suppose you want to add an acl "Principal User:Jane is allowed to produce to any Topic whose name starts with 'Test-' from any host". You can do that by executing the CLI with following options: -
      bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Jane --producer --topic Test- --resource-pattern-type prefixed
      +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Jane --producer --topic Test- --resource-pattern-type prefixed
      Note, --resource-pattern-type defaults to 'literal', which only affects resources with the exact same name or, in the case of the wildcard resource name '*', a resource with any name.
    • Removing Acls
      - Removing acls is pretty much the same. The only difference is instead of --add option users will have to specify --remove option. To remove the acls added by the first example above we can execute the CLI with following options: -
       bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --remove --allow-principal User:Bob --allow-principal User:Alice --allow-host 198.51.100.0 --allow-host 198.51.100.1 --operation Read --operation Write --topic Test-topic 
      + Removing acls is pretty much the same. The only difference is instead of --add option users will have to specify --remove option. To remove the acls added by the first example above we can execute the CLI with following options: +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --remove --allow-principal User:Bob --allow-principal User:Alice --allow-host 198.51.100.0 --allow-host 198.51.100.1 --operation Read --operation Write --topic Test-topic 
      If you want to remove the acl added to the prefixed resource pattern above we can execute the CLI with following options: -
       bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --remove --allow-principal User:Jane --producer --topic Test- --resource-pattern-type Prefixed
    • +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --remove --allow-principal User:Jane --producer --topic Test- --resource-pattern-type Prefixed
    • List Acls
      - We can list acls for any resource by specifying the --list option with the resource. To list all acls on the literal resource pattern Test-topic, we can execute the CLI with following options: -
      bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --list --topic Test-topic
      - However, this will only return the acls that have been added to this exact resource pattern. Other acls can exist that affect access to the topic, - e.g. any acls on the topic wildcard '*', or any acls on prefixed resource patterns. Acls on the wildcard resource pattern can be queried explicitly: -
      bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --list --topic *
      - However, it is not necessarily possible to explicitly query for acls on prefixed resource patterns that match Test-topic as the name of such patterns may not be known. - We can list all acls affecting Test-topic by using '--resource-pattern-type match', e.g. -
      bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --list --topic Test-topic --resource-pattern-type match
      - This will list acls on all matching literal, wildcard and prefixed resource patterns.
    • + We can list acls for any resource by specifying the --list option with the resource. To list all acls on the literal resource pattern Test-topic, we can execute the CLI with following options: +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --list --topic Test-topic
      + However, this will only return the acls that have been added to this exact resource pattern. Other acls can exist that affect access to the topic, + e.g. any acls on the topic wildcard '*', or any acls on prefixed resource patterns. Acls on the wildcard resource pattern can be queried explicitly: +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --list --topic *
      + However, it is not necessarily possible to explicitly query for acls on prefixed resource patterns that match Test-topic as the name of such patterns may not be known. + We can list all acls affecting Test-topic by using '--resource-pattern-type match', e.g. +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --list --topic Test-topic --resource-pattern-type match
      + This will list acls on all matching literal, wildcard and prefixed resource patterns.
    • Adding or removing a principal as producer or consumer
      - The most common use case for acl management are adding/removing a principal as producer or consumer so we added convenience options to handle these cases. In order to add User:Bob as a producer of Test-topic we can execute the following command: -
       bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --producer --topic Test-topic
      - Similarly to add Alice as a consumer of Test-topic with consumer group Group-1 we just have to pass --consumer option: -
       bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --consumer --topic Test-topic --group Group-1 
      - Note that for consumer option we must also specify the consumer group. - In order to remove a principal from producer or consumer role we just need to pass --remove option.
    • + The most common use case for acl management are adding/removing a principal as producer or consumer so we added convenience options to handle these cases. In order to add User:Bob as a producer of Test-topic we can execute the following command: +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --producer --topic Test-topic
      + Similarly to add Alice as a consumer of Test-topic with consumer group Group-1 we just have to pass --consumer option: +
      > bin/kafka-acls.sh --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --consumer --topic Test-topic --group Group-1 
      + Note that for consumer option we must also specify the consumer group. + In order to remove a principal from producer or consumer role we just need to pass --remove option.
    • Admin API based acl management
      Users having Alter permission on ClusterResource can use Admin API for ACL management. kafka-acls.sh script supports AdminClient API to manage ACLs without interacting with zookeeper/authorizer directly. All the above examples can be executed by using --bootstrap-server option. For example: -
      bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config /tmp/adminclient-configs.conf --add --allow-principal User:Bob --producer --topic Test-topic
      +            
      > bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config /tmp/adminclient-configs.conf --add --allow-principal User:Bob --producer --topic Test-topic
       bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config /tmp/adminclient-configs.conf --add --allow-principal User:Bob --consumer --topic Test-topic --group Group-1
       bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config /tmp/adminclient-configs.conf --list --topic Test-topic
    • @@ -1522,8 +1521,8 @@
      Authentication using Delegation Tokens section. + Authentication using Delegation Tokens section. RENEW_DELEGATION_TOKEN (39) Renewing delegation tokens has special rules, for this please see the - Authentication using Delegation Tokens section. + Authentication using Delegation Tokens section. EXPIRE_DELEGATION_TOKEN (40) Expiring delegation tokens has special rules, for this please see the - Authentication using Delegation Tokens section. + Authentication using Delegation Tokens section. DESCRIBE_DELEGATION_TOKEN (41) Describe DelegationToken Describing delegation tokens has special rules, for this please see the - Authentication using Delegation Tokens section. + Authentication using Delegation Tokens section. DELETE_GROUPS (42) @@ -1910,64 +1909,64 @@
      7.5 Incorporating Security Features in a Running Cluster
      - You can secure a running cluster via one or more of the supported protocols discussed previously. This is done in phases: -

      -
        -
      • Incrementally bounce the cluster nodes to open additional secured port(s).
      • -
      • Restart clients using the secured rather than PLAINTEXT port (assuming you are securing the client-broker connection).
      • -
      • Incrementally bounce the cluster again to enable broker-to-broker security (if this is required)
      • -
      • A final incremental bounce to close the PLAINTEXT port.
      • -
      -

      - The specific steps for configuring SSL and SASL are described in sections 7.2 and 7.3. - Follow these steps to enable security for your desired protocol(s). -

      - The security implementation lets you configure different protocols for both broker-client and broker-broker communication. - These must be enabled in separate bounces. A PLAINTEXT port must be left open throughout so brokers and/or clients can continue to communicate. -

      - - When performing an incremental bounce stop the brokers cleanly via a SIGTERM. It's also good practice to wait for restarted replicas to return to the ISR list before moving onto the next node. -

      - As an example, say we wish to encrypt both broker-client and broker-broker communication with SSL. In the first incremental bounce, an SSL port is opened on each node: -
      listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092
      - - We then restart the clients, changing their config to point at the newly opened, secured port: - -
      bootstrap.servers = [broker1:9092,...]
      +    You can secure a running cluster via one or more of the supported protocols discussed previously. This is done in phases:
      +    

      +
        +
      • Incrementally bounce the cluster nodes to open additional secured port(s).
      • +
      • Restart clients using the secured rather than PLAINTEXT port (assuming you are securing the client-broker connection).
      • +
      • Incrementally bounce the cluster again to enable broker-to-broker security (if this is required)
      • +
      • A final incremental bounce to close the PLAINTEXT port.
      • +
      +

      + The specific steps for configuring SSL and SASL are described in sections 7.2 and 7.3. + Follow these steps to enable security for your desired protocol(s). +

      + The security implementation lets you configure different protocols for both broker-client and broker-broker communication. + These must be enabled in separate bounces. A PLAINTEXT port must be left open throughout so brokers and/or clients can continue to communicate. +

      + + When performing an incremental bounce stop the brokers cleanly via a SIGTERM. It's also good practice to wait for restarted replicas to return to the ISR list before moving onto the next node. +

      + As an example, say we wish to encrypt both broker-client and broker-broker communication with SSL. In the first incremental bounce, an SSL port is opened on each node: +
      listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092
      + + We then restart the clients, changing their config to point at the newly opened, secured port: + +
      bootstrap.servers = [broker1:9092,...]
       security.protocol = SSL
       ...etc
      - In the second incremental server bounce we instruct Kafka to use SSL as the broker-broker protocol (which will use the same SSL port): + In the second incremental server bounce we instruct Kafka to use SSL as the broker-broker protocol (which will use the same SSL port): -
      listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092
      +    
      listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092
       security.inter.broker.protocol=SSL
      - In the final bounce we secure the cluster by closing the PLAINTEXT port: + In the final bounce we secure the cluster by closing the PLAINTEXT port: -
      listeners=SSL://broker1:9092
      +    
      listeners=SSL://broker1:9092
       security.inter.broker.protocol=SSL
      - Alternatively we might choose to open multiple ports so that different protocols can be used for broker-broker and broker-client communication. Say we wished to use SSL encryption throughout (i.e. for broker-broker and broker-client communication) but we'd like to add SASL authentication to the broker-client connection also. We would achieve this by opening two additional ports during the first bounce: + Alternatively we might choose to open multiple ports so that different protocols can be used for broker-broker and broker-client communication. Say we wished to use SSL encryption throughout (i.e. for broker-broker and broker-client communication) but we'd like to add SASL authentication to the broker-client connection also. We would achieve this by opening two additional ports during the first bounce: -
      listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092,SASL_SSL://broker1:9093
      +
      listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092,SASL_SSL://broker1:9093
      - We would then restart the clients, changing their config to point at the newly opened, SASL & SSL secured port: + We would then restart the clients, changing their config to point at the newly opened, SASL & SSL secured port: -
      bootstrap.servers = [broker1:9093,...]
      +    
      bootstrap.servers = [broker1:9093,...]
       security.protocol = SASL_SSL
       ...etc
      - The second server bounce would switch the cluster to use encrypted broker-broker communication via the SSL port we previously opened on port 9092: + The second server bounce would switch the cluster to use encrypted broker-broker communication via the SSL port we previously opened on port 9092: -
      listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092,SASL_SSL://broker1:9093
      +    
      listeners=PLAINTEXT://broker1:9091,SSL://broker1:9092,SASL_SSL://broker1:9093
       security.inter.broker.protocol=SSL
      - The final bounce secures the cluster by closing the PLAINTEXT port. + The final bounce secures the cluster by closing the PLAINTEXT port. -
      listeners=SSL://broker1:9092,SASL_SSL://broker1:9093
      +    
      listeners=SSL://broker1:9092,SASL_SSL://broker1:9093
       security.inter.broker.protocol=SSL
      - ZooKeeper can be secured independently of the Kafka cluster. The steps for doing this are covered in section 7.6.2. + ZooKeeper can be secured independently of the Kafka cluster. The steps for doing this are covered in section 7.6.2.

      7.6 ZooKeeper Authentication

      @@ -1981,7 +1980,8 @@

      Here is a sample (partial) Kafka Broker configuration for connecting to ZooKeeper with mTLS authentication. - These configurations are described above in Broker Configs. + These configurations are described above in Broker Configs.

      # connect to the ZooKeeper port configured for TLS
       zookeeper.connect=zk1:2182,zk2:2182,zk3:2182
      @@ -2080,15 +2079,15 @@ 

      It is also possible to turn off authentication in a secure cluster. To do it, follow these steps:

      1. Perform a rolling restart of brokers setting the JAAS login file and/or defining ZooKeeper mutual TLS configurations, which enables brokers to authenticate, but setting zookeeper.set.acl to false. At the end of the rolling restart, brokers stop creating znodes with secure ACLs, but are still able to authenticate and manipulate all znodes
      2. -
      3. Execute the ZkSecurityMigrator tool. To execute the tool, run this script bin/zookeeper-security-migration.sh with zookeeper.acl set to unsecure. This tool traverses the corresponding sub-trees changing the ACLs of the znodes. Use the --zk-tls-config-file <file> option if you need to set TLS configuration.
      4. +
      5. Execute the ZkSecurityMigrator tool. To execute the tool, run this script bin/zookeeper-security-migration.sh with zookeeper.acl set to unsecure. This tool traverses the corresponding sub-trees changing the ACLs of the znodes. Use the --zk-tls-config-file <file> option if you need to set TLS configuration.
      6. If you are disabling mTLS, enable the non-TLS port in ZooKeeper
      7. Perform a second rolling restart of brokers, this time omitting the system property that sets the JAAS login file and/or removing ZooKeeper mutual TLS configuration (including connecting to the non-TLS-enabled ZooKeeper port) as required
      8. If you are disabling mTLS, disable the TLS port in ZooKeeper
      Here is an example of how to run the migration tool: -
      bin/zookeeper-security-migration.sh --zookeeper.acl=secure --zookeeper.connect=localhost:2181
      +
      > bin/zookeeper-security-migration.sh --zookeeper.acl=secure --zookeeper.connect=localhost:2181

      Run this to see the full list of parameters:

      -
      bin/zookeeper-security-migration.sh --help
      +
      > bin/zookeeper-security-migration.sh --help

      7.6.3 Migrating the ZooKeeper ensemble

      It is also necessary to enable SASL and/or mTLS authentication on the ZooKeeper ensemble. To do it, we need to perform a rolling restart of the server and set a few properties. See above for mTLS information. Please refer to the ZooKeeper documentation for more detail:
        diff --git a/docs/upgrade.html b/docs/upgrade.html index 290ade4e9d179..70ca0258db550 100644 --- a/docs/upgrade.html +++ b/docs/upgrade.html @@ -36,11 +36,15 @@
        Notable changes in 3
      1. The Metric#value() method was removed (KAFKA-12573).
      2. The Sum and Total classes were removed (KAFKA-12584). Please use WindowedSum and CumulativeSum instead.
      3. +
      4. The Count and SampledTotal classes were removed. Please use WindowedCount and WindowedSum + respectively instead.
      5. The PrincipalBuilder, DefaultPrincipalBuilder and ResourceFilter classes were removed.
      6. Various constants and constructors were removed from SslConfigs, SaslConfigs, AclBinding and AclBindingFilter.
      7. The Admin.electedPreferredLeaders() methods were removed. Please use Admin.electLeaders instead.
      8. The kafka-preferred-replica-election command line tool was removed. Please use kafka-leader-election instead.
      9. +
      10. The --zookeeper option was removed from the bin/kafka-topics.sh and bin/kafka-reassign-partitions.sh command line tools. + Please use --bootstrap-server instead.
      11. The ConfigEntry constructor was removed (KAFKA-12577). Please use the remaining public constructor instead.
      12. The config value default for the client config client.dns.lookup has been removed. In the unlikely diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 48e9b1232ce73..4315677955db0 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -27,7 +27,7 @@ ext { } // Add Scala version -def defaultScala212Version = '2.12.13' +def defaultScala212Version = '2.12.14' def defaultScala213Version = '2.13.6' if (hasProperty('scalaVersion')) { if (scalaVersion == '2.12') { @@ -65,17 +65,16 @@ versions += [ grgit: "4.1.0", httpclient: "4.5.13", easymock: "4.3", - jackson: "2.10.5", - jacksonDatabind: "2.10.5.1", + jackson: "2.12.3", jacoco: "0.8.7", javassist: "3.27.0-GA", jetty: "9.4.39.v20210325", jersey: "2.34", jline: "3.12.1", - jmh: "1.27", + jmh: "1.32", hamcrest: "2.2", log4j: "1.2.17", - scalaLogging: "3.9.2", + scalaLogging: "3.9.3", jaxb: "2.3.0", jaxrs: "2.1.1", jfreechart: "1.0.0", @@ -104,9 +103,9 @@ versions += [ powermock: "2.0.9", reflections: "0.9.12", rocksDB: "6.19.3", - scalaCollectionCompat: "2.3.0", + scalaCollectionCompat: "2.4.4", scalafmt: "1.5.1", - scalaJava8Compat : "0.9.1", + scalaJava8Compat : "1.0.0", scoverage: "1.4.1", slf4j: "1.7.30", snappy: "1.1.8.1", @@ -131,7 +130,7 @@ libs += [ commonsCli: "commons-cli:commons-cli:$versions.commonsCli", easymock: "org.easymock:easymock:$versions.easymock", jacksonAnnotations: "com.fasterxml.jackson.core:jackson-annotations:$versions.jackson", - jacksonDatabind: "com.fasterxml.jackson.core:jackson-databind:$versions.jacksonDatabind", + jacksonDatabind: "com.fasterxml.jackson.core:jackson-databind:$versions.jackson", jacksonDataformatCsv: "com.fasterxml.jackson.dataformat:jackson-dataformat-csv:$versions.jackson", jacksonModuleScala: "com.fasterxml.jackson.module:jackson-module-scala_$versions.baseScala:$versions.jackson", jacksonJDK8Datatypes: "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:$versions.jackson", diff --git a/gradle/spotbugs-exclude.xml b/gradle/spotbugs-exclude.xml index ab60dfdc7d0dd..a4e894cccc26a 100644 --- a/gradle/spotbugs-exclude.xml +++ b/gradle/spotbugs-exclude.xml @@ -421,12 +421,6 @@ For a detailed description of spotbugs bug categories, see https://spotbugs.read - - - - - - diff --git a/jmh-benchmarks/README.md b/jmh-benchmarks/README.md index 6993f3b2c9c72..50db05895010a 100644 --- a/jmh-benchmarks/README.md +++ b/jmh-benchmarks/README.md @@ -45,6 +45,11 @@ With flame graph output (the semicolon is escaped to ensure it is not treated as ./jmh-benchmarks/jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so\;output=flamegraph +Simultaneous cpu, allocation and lock profiling with async profiler 2.0 and jfr output (the semicolon is +escaped to ensure it is not treated as a command separator): + + ./jmh-benchmarks/jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so\;output=jfr\;alloc\;lock LRUCacheBenchmark + A number of arguments can be passed to configure async profiler, run the following for a description: ./jmh-benchmarks/jmh.sh -prof async:help diff --git a/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/BaseRecordBatchBenchmark.java b/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/BaseRecordBatchBenchmark.java index 30f908ea0881f..e9910da57c00a 100644 --- a/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/BaseRecordBatchBenchmark.java +++ b/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/BaseRecordBatchBenchmark.java @@ -17,9 +17,9 @@ package org.apache.kafka.jmh.record; import kafka.server.BrokerTopicStats; +import kafka.server.RequestLocal; import org.apache.kafka.common.header.Header; import org.apache.kafka.common.record.AbstractRecords; -import org.apache.kafka.common.utils.BufferSupplier; import org.apache.kafka.common.record.CompressionType; import org.apache.kafka.common.record.MemoryRecords; import org.apache.kafka.common.record.MemoryRecordsBuilder; @@ -74,7 +74,7 @@ public enum Bytes { // Used by measureVariableBatchSize ByteBuffer[] batchBuffers; - BufferSupplier bufferSupplier; + RequestLocal requestLocal; final BrokerTopicStats brokerTopicStats = new BrokerTopicStats(); @Setup @@ -85,9 +85,9 @@ public void init() { startingOffset = messageVersion == 2 ? 0 : 42; if (bufferSupplierStr.equals("NO_CACHING")) { - bufferSupplier = BufferSupplier.NO_CACHING; + requestLocal = RequestLocal.NoCaching(); } else if (bufferSupplierStr.equals("CREATE")) { - bufferSupplier = BufferSupplier.create(); + requestLocal = RequestLocal.withThreadConfinedCaching(); } else { throw new IllegalArgumentException("Unsupported buffer supplier " + bufferSupplierStr); } diff --git a/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/CompressedRecordBatchValidationBenchmark.java b/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/CompressedRecordBatchValidationBenchmark.java index f176e0676ab64..24ac53e7866d3 100644 --- a/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/CompressedRecordBatchValidationBenchmark.java +++ b/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/CompressedRecordBatchValidationBenchmark.java @@ -59,6 +59,7 @@ public void measureValidateMessagesAndAssignOffsetsCompressed(Blackhole bh) { false, messageVersion, TimestampType.CREATE_TIME, Long.MAX_VALUE, 0, new AppendOrigin.Client$(), ApiVersion.latestVersion(), - brokerTopicStats); + brokerTopicStats, + requestLocal); } } diff --git a/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/RecordBatchIterationBenchmark.java b/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/RecordBatchIterationBenchmark.java index 8aaa2d558fcda..c331cd584041c 100644 --- a/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/RecordBatchIterationBenchmark.java +++ b/jmh-benchmarks/src/main/java/org/apache/kafka/jmh/record/RecordBatchIterationBenchmark.java @@ -32,8 +32,6 @@ import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; -import java.io.IOException; - @State(Scope.Benchmark) @Fork(value = 1) @Warmup(iterations = 5) @@ -49,9 +47,9 @@ CompressionType compressionType() { } @Benchmark - public void measureIteratorForBatchWithSingleMessage(Blackhole bh) throws IOException { + public void measureIteratorForBatchWithSingleMessage(Blackhole bh) { for (RecordBatch batch : MemoryRecords.readableRecords(singleBatchBuffer.duplicate()).batches()) { - try (CloseableIterator iterator = batch.streamingIterator(bufferSupplier)) { + try (CloseableIterator iterator = batch.streamingIterator(requestLocal.bufferSupplier())) { while (iterator.hasNext()) bh.consume(iterator.next()); } @@ -61,10 +59,10 @@ public void measureIteratorForBatchWithSingleMessage(Blackhole bh) throws IOExce @OperationsPerInvocation(value = batchCount) @Fork(jvmArgsAppend = "-Xmx8g") @Benchmark - public void measureStreamingIteratorForVariableBatchSize(Blackhole bh) throws IOException { + public void measureStreamingIteratorForVariableBatchSize(Blackhole bh) { for (int i = 0; i < batchCount; ++i) { for (RecordBatch batch : MemoryRecords.readableRecords(batchBuffers[i].duplicate()).batches()) { - try (CloseableIterator iterator = batch.streamingIterator(bufferSupplier)) { + try (CloseableIterator iterator = batch.streamingIterator(requestLocal.bufferSupplier())) { while (iterator.hasNext()) bh.consume(iterator.next()); } @@ -75,10 +73,10 @@ public void measureStreamingIteratorForVariableBatchSize(Blackhole bh) throws IO @OperationsPerInvocation(value = batchCount) @Fork(jvmArgsAppend = "-Xmx8g") @Benchmark - public void measureSkipIteratorForVariableBatchSize(Blackhole bh) throws IOException { + public void measureSkipIteratorForVariableBatchSize(Blackhole bh) { for (int i = 0; i < batchCount; ++i) { for (MutableRecordBatch batch : MemoryRecords.readableRecords(batchBuffers[i].duplicate()).batches()) { - try (CloseableIterator iterator = batch.skipKeyValueIterator(bufferSupplier)) { + try (CloseableIterator iterator = batch.skipKeyValueIterator(requestLocal.bufferSupplier())) { while (iterator.hasNext()) bh.consume(iterator.next()); } diff --git a/metadata/src/main/java/org/apache/kafka/controller/Controller.java b/metadata/src/main/java/org/apache/kafka/controller/Controller.java index a34b084ea1302..3cb0d26ee6b60 100644 --- a/metadata/src/main/java/org/apache/kafka/controller/Controller.java +++ b/metadata/src/main/java/org/apache/kafka/controller/Controller.java @@ -20,6 +20,8 @@ import org.apache.kafka.clients.admin.AlterConfigOp; import org.apache.kafka.common.Uuid; import org.apache.kafka.common.config.ConfigResource; +import org.apache.kafka.common.message.AllocateProducerIdsRequestData; +import org.apache.kafka.common.message.AllocateProducerIdsResponseData; import org.apache.kafka.common.message.AlterIsrRequestData; import org.apache.kafka.common.message.AlterIsrResponseData; import org.apache.kafka.common.message.AlterPartitionReassignmentsRequestData; @@ -223,6 +225,15 @@ CompletableFuture> alterClientQuotas( Collection quotaAlterations, boolean validateOnly ); + /** + * Allocate a block of producer IDs for transactional and idempotent producers + * @param request The allocate producer IDs request + * @return A future which yields a new producer ID block as a response + */ + CompletableFuture allocateProducerIds( + AllocateProducerIdsRequestData request + ); + /** * Begin writing a controller snapshot. If there was already an ongoing snapshot, it * simply returns information about that snapshot rather than starting a new one. diff --git a/metadata/src/main/java/org/apache/kafka/controller/ProducerIdControlManager.java b/metadata/src/main/java/org/apache/kafka/controller/ProducerIdControlManager.java new file mode 100644 index 0000000000000..924605c6d910f --- /dev/null +++ b/metadata/src/main/java/org/apache/kafka/controller/ProducerIdControlManager.java @@ -0,0 +1,85 @@ +/* + * 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.controller; + +import org.apache.kafka.common.errors.UnknownServerException; +import org.apache.kafka.common.metadata.ProducerIdsRecord; +import org.apache.kafka.server.common.ApiMessageAndVersion; +import org.apache.kafka.server.common.ProducerIdsBlock; +import org.apache.kafka.timeline.SnapshotRegistry; +import org.apache.kafka.timeline.TimelineLong; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + + +public class ProducerIdControlManager { + + private final ClusterControlManager clusterControlManager; + private final TimelineLong lastProducerId; + + ProducerIdControlManager(ClusterControlManager clusterControlManager, SnapshotRegistry snapshotRegistry) { + this.clusterControlManager = clusterControlManager; + this.lastProducerId = new TimelineLong(snapshotRegistry, 0L); + } + + ControllerResult generateNextProducerId(int brokerId, long brokerEpoch) { + clusterControlManager.checkBrokerEpoch(brokerId, brokerEpoch); + + long producerId = lastProducerId.get(); + + if (producerId > Long.MAX_VALUE - ProducerIdsBlock.PRODUCER_ID_BLOCK_SIZE) { + throw new UnknownServerException("Exhausted all producerIds as the next block's end producerId " + + "is will has exceeded long type limit"); + } + + long nextProducerId = producerId + ProducerIdsBlock.PRODUCER_ID_BLOCK_SIZE; + ProducerIdsRecord record = new ProducerIdsRecord() + .setProducerIdsEnd(nextProducerId) + .setBrokerId(brokerId) + .setBrokerEpoch(brokerEpoch); + ProducerIdsBlock block = new ProducerIdsBlock(brokerId, producerId, ProducerIdsBlock.PRODUCER_ID_BLOCK_SIZE); + return ControllerResult.of(Collections.singletonList(new ApiMessageAndVersion(record, (short) 0)), block); + } + + void replay(ProducerIdsRecord record) { + long currentProducerId = lastProducerId.get(); + if (record.producerIdsEnd() <= currentProducerId) { + throw new RuntimeException("Producer ID from record is not monotonically increasing"); + } else { + lastProducerId.set(record.producerIdsEnd()); + } + } + + Iterator> iterator(long epoch) { + List records = new ArrayList<>(1); + + long producerId = lastProducerId.get(epoch); + if (producerId > 0) { + records.add(new ApiMessageAndVersion( + new ProducerIdsRecord() + .setProducerIdsEnd(producerId) + .setBrokerId(0) + .setBrokerEpoch(0L), + (short) 0)); + } + return Collections.singleton(records).iterator(); + } +} diff --git a/metadata/src/main/java/org/apache/kafka/controller/QuorumController.java b/metadata/src/main/java/org/apache/kafka/controller/QuorumController.java index 746d9068488d9..bee0b69eff4e3 100644 --- a/metadata/src/main/java/org/apache/kafka/controller/QuorumController.java +++ b/metadata/src/main/java/org/apache/kafka/controller/QuorumController.java @@ -24,6 +24,8 @@ import org.apache.kafka.common.errors.ApiException; import org.apache.kafka.common.errors.NotControllerException; import org.apache.kafka.common.errors.UnknownServerException; +import org.apache.kafka.common.message.AllocateProducerIdsRequestData; +import org.apache.kafka.common.message.AllocateProducerIdsResponseData; import org.apache.kafka.common.message.AlterIsrRequestData; import org.apache.kafka.common.message.AlterIsrResponseData; import org.apache.kafka.common.message.AlterPartitionReassignmentsRequestData; @@ -44,6 +46,7 @@ import org.apache.kafka.common.metadata.MetadataRecordType; import org.apache.kafka.common.metadata.PartitionChangeRecord; import org.apache.kafka.common.metadata.PartitionRecord; +import org.apache.kafka.common.metadata.ProducerIdsRecord; import org.apache.kafka.common.metadata.QuotaRecord; import org.apache.kafka.common.metadata.RegisterBrokerRecord; import org.apache.kafka.common.metadata.RemoveTopicRecord; @@ -359,7 +362,8 @@ void createSnapshotGenerator(long epoch) { new Section("cluster", clusterControl.iterator(epoch)), new Section("replication", replicationControl.iterator(epoch)), new Section("configuration", configurationControl.iterator(epoch)), - new Section("clientQuotas", clientQuotaControlManager.iterator(epoch)))); + new Section("clientQuotas", clientQuotaControlManager.iterator(epoch)), + new Section("producerIds", producerIdControlManager.iterator(epoch)))); reschedule(0); } @@ -855,6 +859,9 @@ private void replay(ApiMessage message, Optional snapshotId, lon case QUOTA_RECORD: clientQuotaControlManager.replay((QuotaRecord) message); break; + case PRODUCER_IDS_RECORD: + producerIdControlManager.replay((ProducerIdsRecord) message); + break; default: throw new RuntimeException("Unhandled record type " + type); } @@ -929,6 +936,12 @@ private void replay(ApiMessage message, Optional snapshotId, lon */ private final FeatureControlManager featureControl; + /** + * An object which stores the controller's view of the latest producer ID + * that has been generated. This must be accessed only by the event queue thread. + */ + private final ProducerIdControlManager producerIdControlManager; + /** * An object which stores the controller's view of topics and partitions. * This must be accessed only by the event queue thread. @@ -995,6 +1008,7 @@ private QuorumController(LogContext logContext, this.clusterControl = new ClusterControlManager(logContext, time, snapshotRegistry, sessionTimeoutNs, replicaPlacer); this.featureControl = new FeatureControlManager(supportedFeatures, snapshotRegistry); + this.producerIdControlManager = new ProducerIdControlManager(clusterControl, snapshotRegistry); this.snapshotGeneratorManager = new SnapshotGeneratorManager(snapshotWriterBuilder); this.replicationControl = new ReplicationControlManager(snapshotRegistry, logContext, defaultReplicationFactor, defaultNumPartitions, @@ -1199,6 +1213,16 @@ public CompletableFuture> alterClientQuotas( }); } + @Override + public CompletableFuture allocateProducerIds( + AllocateProducerIdsRequestData request) { + return appendWriteEvent("allocateProducerIds", + () -> producerIdControlManager.generateNextProducerId(request.brokerId(), request.brokerEpoch())) + .thenApply(result -> new AllocateProducerIdsResponseData() + .setProducerIdStart(result.producerIdStart()) + .setProducerIdLen(result.producerIdLen())); + } + @Override public CompletableFuture> createPartitions(long deadlineNs, List topics) { diff --git a/metadata/src/main/java/org/apache/kafka/controller/ResultOrError.java b/metadata/src/main/java/org/apache/kafka/controller/ResultOrError.java index 2fedacdb5e1c8..6a548c4e40255 100644 --- a/metadata/src/main/java/org/apache/kafka/controller/ResultOrError.java +++ b/metadata/src/main/java/org/apache/kafka/controller/ResultOrError.java @@ -42,6 +42,14 @@ public ResultOrError(T result) { this.result = result; } + public static ResultOrError of(T result) { + return new ResultOrError<>(result); + } + + public static ResultOrError of(ApiError error) { + return new ResultOrError<>(error); + } + public boolean isError() { return error != null; } diff --git a/metadata/src/main/java/org/apache/kafka/controller/StripedReplicaPlacer.java b/metadata/src/main/java/org/apache/kafka/controller/StripedReplicaPlacer.java index a2aadc5cd4da8..031354c56dab7 100644 --- a/metadata/src/main/java/org/apache/kafka/controller/StripedReplicaPlacer.java +++ b/metadata/src/main/java/org/apache/kafka/controller/StripedReplicaPlacer.java @@ -340,14 +340,14 @@ List> rackNames() { } List place(int replicationFactor) { - if (replicationFactor <= 0) { - throw new InvalidReplicationFactorException("Invalid replication factor " + - replicationFactor + ": the replication factor must be positive."); - } + throwInvalidReplicationFactorIfNonPositive(replicationFactor); + throwInvalidReplicationFactorIfTooFewBrokers(replicationFactor, numTotalBrokers()); + throwInvalidReplicationFactorIfZero(numUnfencedBrokers()); // If we have returned as many assignments as there are unfenced brokers in // the cluster, shuffle the rack list and broker lists to try to avoid // repeating the same assignments again. - if (epoch == numUnfencedBrokers) { + // But don't reset the iteration epoch for a single unfenced broker -- otherwise we would loop forever + if (epoch == numUnfencedBrokers && numUnfencedBrokers > 1) { shuffle(); epoch = 0; } @@ -400,6 +400,27 @@ void shuffle() { } } + private static void throwInvalidReplicationFactorIfNonPositive(int replicationFactor) { + if (replicationFactor <= 0) { + throw new InvalidReplicationFactorException("Invalid replication factor " + + replicationFactor + ": the replication factor must be positive."); + } + } + + private static void throwInvalidReplicationFactorIfZero(int numUnfenced) { + if (numUnfenced == 0) { + throw new InvalidReplicationFactorException("All brokers are currently fenced."); + } + } + + private static void throwInvalidReplicationFactorIfTooFewBrokers(int replicationFactor, int numTotalBrokers) { + if (replicationFactor > numTotalBrokers) { + throw new InvalidReplicationFactorException("The target replication factor " + + "of " + replicationFactor + " cannot be reached because only " + + numTotalBrokers + " broker(s) are registered."); + } + } + private final Random random; public StripedReplicaPlacer(Random random) { @@ -412,14 +433,9 @@ public List> place(int startPartition, short replicationFactor, Iterator iterator) { RackList rackList = new RackList(random, iterator); - if (rackList.numUnfencedBrokers() == 0) { - throw new InvalidReplicationFactorException("All brokers are currently fenced."); - } - if (replicationFactor > rackList.numTotalBrokers()) { - throw new InvalidReplicationFactorException("The target replication factor " + - "of " + replicationFactor + " cannot be reached because only " + - rackList.numTotalBrokers() + " broker(s) are registered."); - } + throwInvalidReplicationFactorIfNonPositive(replicationFactor); + throwInvalidReplicationFactorIfZero(rackList.numUnfencedBrokers()); + throwInvalidReplicationFactorIfTooFewBrokers(replicationFactor, rackList.numTotalBrokers()); List> placements = new ArrayList<>(numPartitions); for (int partition = 0; partition < numPartitions; partition++) { placements.add(rackList.place(replicationFactor)); diff --git a/metadata/src/main/java/org/apache/kafka/metadata/MetadataParser.java b/metadata/src/main/java/org/apache/kafka/metadata/MetadataParser.java deleted file mode 100644 index d172dc7db33c9..0000000000000 --- a/metadata/src/main/java/org/apache/kafka/metadata/MetadataParser.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.metadata; - -import org.apache.kafka.common.metadata.MetadataRecordType; -import org.apache.kafka.common.protocol.ApiMessage; -import org.apache.kafka.common.protocol.ByteBufferAccessor; -import org.apache.kafka.common.protocol.ObjectSerializationCache; -import org.apache.kafka.common.utils.ByteUtils; - -import java.nio.ByteBuffer; - -public class MetadataParser { - public static final int MAX_SERIALIZED_EVENT_SIZE = 32 * 1024 * 1024; - - private static short unsignedIntToShort(int val, String entity) { - if (val > Short.MAX_VALUE) { - throw new MetadataParseException("Value for " + entity + " was too large."); - } - return (short) val; - } - - /** - * Parse the given buffer. - * - * @param buffer The buffer. Its offsets will be modified. - * - * @return The metadata message. - */ - public static ApiMessage read(ByteBuffer buffer) { - short type; - try { - type = unsignedIntToShort(ByteUtils.readUnsignedVarint(buffer), "type"); - } catch (Exception e) { - throw new MetadataParseException("Failed to read variable-length type " + - "number: " + e.getClass().getSimpleName() + ": " + e.getMessage()); - } - short version; - try { - version = unsignedIntToShort(ByteUtils.readUnsignedVarint(buffer), "version"); - } catch (Exception e) { - throw new MetadataParseException("Failed to read variable-length " + - "version number: " + e.getClass().getSimpleName() + ": " + e.getMessage()); - } - MetadataRecordType recordType = MetadataRecordType.fromId(type); - ApiMessage message = recordType.newMetadataRecord(); - try { - message.read(new ByteBufferAccessor(buffer), version); - } catch (Exception e) { - throw new MetadataParseException(recordType + "#parse failed: " + - e.getClass().getSimpleName() + ": " + e.getMessage()); - } - if (buffer.hasRemaining()) { - throw new MetadataParseException("Found " + buffer.remaining() + - " byte(s) of garbage after " + recordType); - } - return message; - } - - /** - * Find the size of an API message and set up its ObjectSerializationCache. - * - * @param message The metadata message. - * @param version The metadata message version. - * @param cache The object serialization cache to use. - * - * @return The size - */ - public static int size(ApiMessage message, - short version, - ObjectSerializationCache cache) { - long messageSize = message.size(cache, version); - long totalSize = messageSize + - ByteUtils.sizeOfUnsignedVarint(message.apiKey()) + - ByteUtils.sizeOfUnsignedVarint(version); - if (totalSize > MAX_SERIALIZED_EVENT_SIZE) { - throw new RuntimeException("Event size would be " + totalSize + ", but the " + - "maximum serialized event size is " + MAX_SERIALIZED_EVENT_SIZE); - } - return (int) totalSize; - } - - /** - * Convert the given metadata message into a ByteBuffer. - * - * @param message The metadata message. - * @param version The metadata message version. - * @param cache The object serialization cache to use. This must have been - * initialized by calling size() previously. - * @param buf The buffer to write to. - */ - public static void write(ApiMessage message, - short version, - ObjectSerializationCache cache, - ByteBuffer buf) { - ByteUtils.writeUnsignedVarint(message.apiKey(), buf); - ByteUtils.writeUnsignedVarint(version, buf); - message.write(new ByteBufferAccessor(buf), cache, version); - } -} diff --git a/metadata/src/main/java/org/apache/kafka/timeline/TimelineLong.java b/metadata/src/main/java/org/apache/kafka/timeline/TimelineLong.java index e057391c4bcd3..36a300ff94998 100644 --- a/metadata/src/main/java/org/apache/kafka/timeline/TimelineLong.java +++ b/metadata/src/main/java/org/apache/kafka/timeline/TimelineLong.java @@ -47,8 +47,12 @@ public void mergeFrom(long destinationEpoch, Delta delta) { private long value; public TimelineLong(SnapshotRegistry snapshotRegistry) { + this(snapshotRegistry, 0L); + } + + public TimelineLong(SnapshotRegistry snapshotRegistry, long value) { this.snapshotRegistry = snapshotRegistry; - this.value = 0; + this.value = value; } public long get() { diff --git a/metadata/src/main/resources/common/metadata/ProducerIdsRecord.json b/metadata/src/main/resources/common/metadata/ProducerIdsRecord.json new file mode 100644 index 0000000000000..09e6b536129e2 --- /dev/null +++ b/metadata/src/main/resources/common/metadata/ProducerIdsRecord.json @@ -0,0 +1,29 @@ +// 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. + +{ + "apiKey": 15, + "type": "metadata", + "name": "ProducerIdsRecord", + "validVersions": "0", + "fields": [ + { "name": "BrokerId", "type": "int32", "versions": "0+", "entityType": "brokerId", + "about": "The ID of the requesting broker" }, + { "name": "BrokerEpoch", "type": "int64", "versions": "0+", "default": "-1", + "about": "The epoch of the requesting broker" }, + { "name": "ProducerIdsEnd", "type": "int64", "versions": "0+", + "about": "The highest producer ID that has been generated"} + ] +} diff --git a/metadata/src/test/java/org/apache/kafka/controller/ProducerIdControlManagerTest.java b/metadata/src/test/java/org/apache/kafka/controller/ProducerIdControlManagerTest.java new file mode 100644 index 0000000000000..f96510ddae11e --- /dev/null +++ b/metadata/src/test/java/org/apache/kafka/controller/ProducerIdControlManagerTest.java @@ -0,0 +1,173 @@ +/* + * 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.controller; + +import org.apache.kafka.common.errors.StaleBrokerEpochException; +import org.apache.kafka.common.errors.UnknownServerException; +import org.apache.kafka.common.metadata.ProducerIdsRecord; +import org.apache.kafka.common.metadata.RegisterBrokerRecord; +import org.apache.kafka.common.security.auth.SecurityProtocol; +import org.apache.kafka.common.utils.LogContext; +import org.apache.kafka.common.utils.MockTime; +import org.apache.kafka.server.common.ApiMessageAndVersion; +import org.apache.kafka.server.common.ProducerIdsBlock; +import org.apache.kafka.timeline.SnapshotRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Iterator; +import java.util.List; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class ProducerIdControlManagerTest { + + private SnapshotRegistry snapshotRegistry; + private ClusterControlManager clusterControl; + private ProducerIdControlManager producerIdControlManager; + + @BeforeEach + public void setUp() { + final LogContext logContext = new LogContext(); + final MockTime time = new MockTime(); + final Random random = new Random(); + snapshotRegistry = new SnapshotRegistry(logContext); + clusterControl = new ClusterControlManager( + logContext, time, snapshotRegistry, 1000, + new StripedReplicaPlacer(random)); + + clusterControl.activate(); + for (int i = 0; i < 4; i++) { + RegisterBrokerRecord brokerRecord = new RegisterBrokerRecord().setBrokerEpoch(100).setBrokerId(i); + brokerRecord.endPoints().add(new RegisterBrokerRecord.BrokerEndpoint(). + setSecurityProtocol(SecurityProtocol.PLAINTEXT.id). + setPort((short) 9092). + setName("PLAINTEXT"). + setHost(String.format("broker-%02d.example.org", i))); + clusterControl.replay(brokerRecord); + } + + this.producerIdControlManager = new ProducerIdControlManager(clusterControl, snapshotRegistry); + } + + @Test + public void testInitialResult() { + ControllerResult result = + producerIdControlManager.generateNextProducerId(1, 100); + assertEquals(0, result.response().producerIdStart()); + assertEquals(1000, result.response().producerIdLen()); + ProducerIdsRecord record = (ProducerIdsRecord) result.records().get(0).message(); + assertEquals(1000, record.producerIdsEnd()); + } + + @Test + public void testMonotonic() { + producerIdControlManager.replay( + new ProducerIdsRecord() + .setBrokerId(1) + .setBrokerEpoch(100) + .setProducerIdsEnd(42)); + + ProducerIdsBlock range = + producerIdControlManager.generateNextProducerId(1, 100).response(); + assertEquals(42, range.producerIdStart()); + + // Can't go backwards in Producer IDs + assertThrows(RuntimeException.class, () -> { + producerIdControlManager.replay( + new ProducerIdsRecord() + .setBrokerId(1) + .setBrokerEpoch(100) + .setProducerIdsEnd(40)); + }, "Producer ID range must only increase"); + range = producerIdControlManager.generateNextProducerId(1, 100).response(); + assertEquals(42, range.producerIdStart()); + + // Gaps in the ID range are okay. + producerIdControlManager.replay( + new ProducerIdsRecord() + .setBrokerId(1) + .setBrokerEpoch(100) + .setProducerIdsEnd(50)); + range = producerIdControlManager.generateNextProducerId(1, 100).response(); + assertEquals(50, range.producerIdStart()); + } + + @Test + public void testUnknownBrokerOrEpoch() { + ControllerResult result; + + assertThrows(StaleBrokerEpochException.class, () -> + producerIdControlManager.generateNextProducerId(99, 0)); + + assertThrows(StaleBrokerEpochException.class, () -> + producerIdControlManager.generateNextProducerId(1, 99)); + } + + @Test + public void testMaxValue() { + producerIdControlManager.replay( + new ProducerIdsRecord() + .setBrokerId(1) + .setBrokerEpoch(100) + .setProducerIdsEnd(Long.MAX_VALUE - 1)); + + assertThrows(UnknownServerException.class, () -> + producerIdControlManager.generateNextProducerId(1, 100)); + } + + @Test + public void testSnapshotIterator() { + ProducerIdsBlock range = null; + for (int i = 0; i < 100; i++) { + range = generateProducerIds(producerIdControlManager, i % 4, 100); + } + + Iterator> snapshotIterator = producerIdControlManager.iterator(Long.MAX_VALUE); + assertTrue(snapshotIterator.hasNext()); + List batch = snapshotIterator.next(); + assertEquals(1, batch.size(), "Producer IDs record batch should only contain a single record"); + assertEquals(range.producerIdStart() + range.producerIdLen(), ((ProducerIdsRecord) batch.get(0).message()).producerIdsEnd()); + assertFalse(snapshotIterator.hasNext(), "Producer IDs iterator should only contain a single batch"); + + ProducerIdControlManager newProducerIdManager = new ProducerIdControlManager(clusterControl, snapshotRegistry); + snapshotIterator = producerIdControlManager.iterator(Long.MAX_VALUE); + while (snapshotIterator.hasNext()) { + snapshotIterator.next().forEach(message -> newProducerIdManager.replay((ProducerIdsRecord) message.message())); + } + + // Verify that after reloading state from this "snapshot", we don't produce any overlapping IDs + long lastProducerID = range.producerIdStart() + range.producerIdLen() - 1; + range = generateProducerIds(producerIdControlManager, 1, 100); + assertTrue(range.producerIdStart() > lastProducerID); + } + + static ProducerIdsBlock generateProducerIds( + ProducerIdControlManager producerIdControlManager, int brokerId, long brokerEpoch) { + ControllerResult result = + producerIdControlManager.generateNextProducerId(brokerId, brokerEpoch); + result.records().forEach(apiMessageAndVersion -> + producerIdControlManager.replay((ProducerIdsRecord) apiMessageAndVersion.message())); + return result.response(); + } +} diff --git a/metadata/src/test/java/org/apache/kafka/controller/QuorumControllerTest.java b/metadata/src/test/java/org/apache/kafka/controller/QuorumControllerTest.java index c6114dee0891b..5a39f82c98cfc 100644 --- a/metadata/src/test/java/org/apache/kafka/controller/QuorumControllerTest.java +++ b/metadata/src/test/java/org/apache/kafka/controller/QuorumControllerTest.java @@ -34,6 +34,7 @@ import org.apache.kafka.common.Uuid; import org.apache.kafka.common.config.ConfigResource; import org.apache.kafka.common.errors.TimeoutException; +import org.apache.kafka.common.message.AllocateProducerIdsRequestData; import org.apache.kafka.common.message.AlterPartitionReassignmentsRequestData.ReassignableTopic; import org.apache.kafka.common.message.AlterPartitionReassignmentsRequestData; import org.apache.kafka.common.message.AlterPartitionReassignmentsResponseData; @@ -54,6 +55,7 @@ import org.apache.kafka.common.message.ListPartitionReassignmentsRequestData; import org.apache.kafka.common.message.ListPartitionReassignmentsResponseData; import org.apache.kafka.common.metadata.PartitionRecord; +import org.apache.kafka.common.metadata.ProducerIdsRecord; import org.apache.kafka.common.metadata.RegisterBrokerRecord.BrokerEndpoint; import org.apache.kafka.common.metadata.RegisterBrokerRecord.BrokerEndpointCollection; import org.apache.kafka.common.metadata.RegisterBrokerRecord; @@ -267,6 +269,8 @@ public void testSnapshotSaveAndLoad() throws Throwable { setBrokerIds(Arrays.asList(1, 2, 0))). iterator()))).iterator()))).get(); fooId = fooData.topics().find("foo").topicId(); + active.allocateProducerIds( + new AllocateProducerIdsRequestData().setBrokerId(0).setBrokerEpoch(brokerEpochs.get(0))).get(); long snapshotEpoch = active.beginWritingSnapshot().get(); writer = snapshotWriterBuilder.writers.takeFirst(); assertEquals(snapshotEpoch, writer.epoch()); @@ -338,7 +342,11 @@ private void checkSnapshotContents(Uuid fooId, setEndPoints(new BrokerEndpointCollection(Arrays.asList( new BrokerEndpoint().setName("PLAINTEXT").setHost("localhost"). setPort(9095).setSecurityProtocol((short) 0)).iterator())). - setRack(null), (short) 0))), + setRack(null), (short) 0)), + Arrays.asList(new ApiMessageAndVersion(new ProducerIdsRecord(). + setBrokerId(0). + setBrokerEpoch(brokerEpochs.get(0)). + setProducerIdsEnd(1000), (short) 0))), iterator); } diff --git a/metadata/src/test/java/org/apache/kafka/controller/QuorumControllerTestEnv.java b/metadata/src/test/java/org/apache/kafka/controller/QuorumControllerTestEnv.java index db3acfba2a72f..da2269974022a 100644 --- a/metadata/src/test/java/org/apache/kafka/controller/QuorumControllerTestEnv.java +++ b/metadata/src/test/java/org/apache/kafka/controller/QuorumControllerTestEnv.java @@ -53,7 +53,7 @@ public QuorumControllerTestEnv(LocalLogManagerTestEnv logEnv, QuorumController activeController() throws InterruptedException { AtomicReference value = new AtomicReference<>(null); - TestUtils.retryOnExceptionWithTimeout(3, 20000, () -> { + TestUtils.retryOnExceptionWithTimeout(20000, 3, () -> { QuorumController activeController = null; for (QuorumController controller : controllers) { if (controller.isActive()) { diff --git a/metadata/src/test/java/org/apache/kafka/controller/StripedReplicaPlacerTest.java b/metadata/src/test/java/org/apache/kafka/controller/StripedReplicaPlacerTest.java index 667e900dfbfe0..c3fbb0996a5ba 100644 --- a/metadata/src/test/java/org/apache/kafka/controller/StripedReplicaPlacerTest.java +++ b/metadata/src/test/java/org/apache/kafka/controller/StripedReplicaPlacerTest.java @@ -84,6 +84,22 @@ public void testAvoidFencedReplicaIfPossibleOnSingleRack() { assertEquals(Arrays.asList(0, 4, 3, 2), rackList.place(4)); } + /** + * Test that we perform striped replica placement as expected for a multi-partition topic + * on a single unfenced broker + */ + @Test + public void testMultiPartitionTopicPlacementOnSingleUnfencedBroker() { + MockRandom random = new MockRandom(); + StripedReplicaPlacer placer = new StripedReplicaPlacer(random); + assertEquals(Arrays.asList(Arrays.asList(0), + Arrays.asList(0), + Arrays.asList(0)), + placer.place(0, 3, (short) 1, Arrays.asList( + new UsableBroker(0, Optional.empty(), false), + new UsableBroker(1, Optional.empty(), true)).iterator())); + } + /** * Test that we will place on the fenced replica if we need to. */ @@ -167,6 +183,17 @@ public void testNotEnoughBrokers() { new UsableBroker(10, Optional.of("1"), false)).iterator())).getMessage()); } + @Test + public void testNonPositiveReplicationFactor() { + MockRandom random = new MockRandom(); + StripedReplicaPlacer placer = new StripedReplicaPlacer(random); + assertEquals("Invalid replication factor 0: the replication factor must be positive.", + assertThrows(InvalidReplicationFactorException.class, + () -> placer.place(0, 1, (short) 0, Arrays.asList( + new UsableBroker(11, Optional.of("1"), false), + new UsableBroker(10, Optional.of("1"), false)).iterator())).getMessage()); + } + @Test public void testSuccessfulPlacement() { MockRandom random = new MockRandom(); @@ -210,4 +237,42 @@ public void testEvenDistribution() { assertEquals(11, counts.get(Arrays.asList(3, 2))); } + @Test + public void testRackListAllBrokersFenced() { + // ensure we can place N replicas on a rack when the rack has less than N brokers + MockRandom random = new MockRandom(); + RackList rackList = new RackList(random, Arrays.asList( + new UsableBroker(0, Optional.empty(), true), + new UsableBroker(1, Optional.empty(), true), + new UsableBroker(2, Optional.empty(), true)).iterator()); + assertEquals(3, rackList.numTotalBrokers()); + assertEquals(0, rackList.numUnfencedBrokers()); + assertEquals(Collections.singletonList(Optional.empty()), rackList.rackNames()); + assertEquals("All brokers are currently fenced.", + assertThrows(InvalidReplicationFactorException.class, + () -> rackList.place(3)).getMessage()); + } + + @Test + public void testRackListNotEnoughBrokers() { + MockRandom random = new MockRandom(); + RackList rackList = new RackList(random, Arrays.asList( + new UsableBroker(11, Optional.of("1"), false), + new UsableBroker(10, Optional.of("1"), false)).iterator()); + assertEquals("The target replication factor of 3 cannot be reached because only " + + "2 broker(s) are registered.", + assertThrows(InvalidReplicationFactorException.class, + () -> rackList.place(3)).getMessage()); + } + + @Test + public void testRackListNonPositiveReplicationFactor() { + MockRandom random = new MockRandom(); + RackList rackList = new RackList(random, Arrays.asList( + new UsableBroker(11, Optional.of("1"), false), + new UsableBroker(10, Optional.of("1"), false)).iterator()); + assertEquals("Invalid replication factor -1: the replication factor must be positive.", + assertThrows(InvalidReplicationFactorException.class, + () -> rackList.place(-1)).getMessage()); + } } diff --git a/metadata/src/test/java/org/apache/kafka/metadata/MetadataParserTest.java b/metadata/src/test/java/org/apache/kafka/metadata/MetadataParserTest.java deleted file mode 100644 index 41e968c4d5c8d..0000000000000 --- a/metadata/src/test/java/org/apache/kafka/metadata/MetadataParserTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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.metadata; - -import org.apache.kafka.common.metadata.ConfigRecord; -import org.apache.kafka.common.metadata.PartitionRecord; -import org.apache.kafka.common.metadata.RegisterBrokerRecord; -import org.apache.kafka.common.protocol.ApiMessage; -import org.apache.kafka.common.protocol.ObjectSerializationCache; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Timeout(value = 40) -public class MetadataParserTest { - private static final Logger log = - LoggerFactory.getLogger(MetadataParserTest.class); - - /** - * Test some serialization / deserialization round trips. - */ - @Test - public void testRoundTrips() { - testRoundTrip(new RegisterBrokerRecord().setBrokerId(1).setBrokerEpoch(2), (short) 0); - testRoundTrip(new ConfigRecord().setName("my.config.value"). - setResourceName("foo").setResourceType((byte) 0).setValue("bar"), (short) 0); - } - - private static void testRoundTrip(ApiMessage message, short version) { - ObjectSerializationCache cache = new ObjectSerializationCache(); - int size = MetadataParser.size(message, version, cache); - ByteBuffer buffer = ByteBuffer.allocate(size); - MetadataParser.write(message, version, cache, buffer); - buffer.flip(); - ApiMessage message2 = MetadataParser.read(buffer.duplicate()); - assertEquals(message, message2); - assertEquals(message2, message); - - ObjectSerializationCache cache2 = new ObjectSerializationCache(); - int size2 = MetadataParser.size(message2, version, cache2); - assertEquals(size, size2); - ByteBuffer buffer2 = ByteBuffer.allocate(size); - MetadataParser.write(message2, version, cache2, buffer2); - buffer2.flip(); - assertEquals(buffer.duplicate(), buffer2.duplicate()); - } - - /** - * Test attempting to serialize a message which is too big to be serialized. - */ - @Test - public void testMaxSerializedEventSizeCheck() { - List longReplicaList = - new ArrayList<>(MetadataParser.MAX_SERIALIZED_EVENT_SIZE / Integer.BYTES); - for (int i = 0; i < MetadataParser.MAX_SERIALIZED_EVENT_SIZE / Integer.BYTES; i++) { - longReplicaList.add(i); - } - PartitionRecord partitionRecord = new PartitionRecord(). - setReplicas(longReplicaList); - ObjectSerializationCache cache = new ObjectSerializationCache(); - assertEquals("Event size would be 33554482, but the maximum serialized event " + - "size is 33554432", assertThrows(RuntimeException.class, () -> { - MetadataParser.size(partitionRecord, (short) 0, cache); - }).getMessage()); - } - - /** - * Test attemping to parse an event which has a malformed message type varint. - */ - @Test - public void testParsingMalformedMessageTypeVarint() { - ByteBuffer buffer = ByteBuffer.allocate(64); - buffer.clear(); - buffer.put((byte) 0x80); - buffer.put((byte) 0x80); - buffer.put((byte) 0x80); - buffer.put((byte) 0x80); - buffer.put((byte) 0x80); - buffer.put((byte) 0x80); - buffer.position(0); - buffer.limit(64); - assertStartsWith("Failed to read variable-length type number", - assertThrows(MetadataParseException.class, () -> { - MetadataParser.read(buffer); - }).getMessage()); - } - - /** - * Test attemping to parse an event which has a malformed message version varint. - */ - @Test - public void testParsingMalformedMessageVersionVarint() { - ByteBuffer buffer = ByteBuffer.allocate(64); - buffer.clear(); - buffer.put((byte) 0x00); - buffer.put((byte) 0x80); - buffer.put((byte) 0x80); - buffer.put((byte) 0x80); - buffer.put((byte) 0x80); - buffer.put((byte) 0x80); - buffer.put((byte) 0x80); - buffer.position(0); - buffer.limit(64); - assertStartsWith("Failed to read variable-length version number", - assertThrows(MetadataParseException.class, () -> { - MetadataParser.read(buffer); - }).getMessage()); - } - - /** - * Test attemping to parse an event which has a malformed message version varint. - */ - @Test - public void testParsingRecordWithGarbageAtEnd() { - RegisterBrokerRecord message = new RegisterBrokerRecord().setBrokerId(1).setBrokerEpoch(2); - ObjectSerializationCache cache = new ObjectSerializationCache(); - int size = MetadataParser.size(message, (short) 0, cache); - ByteBuffer buffer = ByteBuffer.allocate(size + 1); - MetadataParser.write(message, (short) 0, cache, buffer); - buffer.clear(); - assertStartsWith("Found 1 byte(s) of garbage after", - assertThrows(MetadataParseException.class, () -> { - MetadataParser.read(buffer); - }).getMessage()); - } - - private static void assertStartsWith(String prefix, String str) { - assertTrue(str.startsWith(prefix), - "Expected string '" + str + "' to start with '" + prefix + "'"); - } -} diff --git a/metadata/src/test/java/org/apache/kafka/metadata/MetadataRecordSerdeTest.java b/metadata/src/test/java/org/apache/kafka/metadata/MetadataRecordSerdeTest.java index 77906e7141380..3a25b8da63ca3 100644 --- a/metadata/src/test/java/org/apache/kafka/metadata/MetadataRecordSerdeTest.java +++ b/metadata/src/test/java/org/apache/kafka/metadata/MetadataRecordSerdeTest.java @@ -17,18 +17,20 @@ package org.apache.kafka.metadata; import org.apache.kafka.common.Uuid; -import org.apache.kafka.common.errors.SerializationException; +import org.apache.kafka.common.metadata.RegisterBrokerRecord; import org.apache.kafka.common.metadata.TopicRecord; import org.apache.kafka.common.protocol.ByteBufferAccessor; import org.apache.kafka.common.protocol.ObjectSerializationCache; import org.apache.kafka.common.utils.ByteUtils; import org.apache.kafka.server.common.ApiMessageAndVersion; +import org.apache.kafka.server.common.serialization.MetadataParseException; import org.junit.jupiter.api.Test; import java.nio.ByteBuffer; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class MetadataRecordSerdeTest { @@ -65,8 +67,158 @@ public void testDeserializeWithUnhandledFrameVersion() { buffer.flip(); MetadataRecordSerde serde = new MetadataRecordSerde(); - assertThrows(SerializationException.class, - () -> serde.read(new ByteBufferAccessor(buffer), 16)); + assertStartsWith("Could not deserialize metadata record due to unknown frame version", + assertThrows(MetadataParseException.class, + () -> serde.read(new ByteBufferAccessor(buffer), 16)).getMessage()); + } + + /** + * Test attempting to parse an event which has a malformed frame version type varint. + */ + @Test + public void testParsingMalformedFrameVersionVarint() { + MetadataRecordSerde serde = new MetadataRecordSerde(); + ByteBuffer buffer = ByteBuffer.allocate(64); + buffer.clear(); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.position(0); + buffer.limit(64); + assertStartsWith("Error while reading frame version", + assertThrows(MetadataParseException.class, + () -> serde.read(new ByteBufferAccessor(buffer), buffer.remaining())).getMessage()); + } + + /** + * Test attempting to parse an event which has a malformed message type varint. + */ + @Test + public void testParsingMalformedMessageTypeVarint() { + MetadataRecordSerde serde = new MetadataRecordSerde(); + ByteBuffer buffer = ByteBuffer.allocate(64); + buffer.clear(); + buffer.put((byte) 0x00); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.position(0); + buffer.limit(64); + assertStartsWith("Error while reading type", + assertThrows(MetadataParseException.class, + () -> serde.read(new ByteBufferAccessor(buffer), buffer.remaining())).getMessage()); + } + + /** + * Test attempting to parse an event which has a malformed message version varint. + */ + @Test + public void testParsingMalformedMessageVersionVarint() { + MetadataRecordSerde serde = new MetadataRecordSerde(); + ByteBuffer buffer = ByteBuffer.allocate(64); + buffer.clear(); + buffer.put((byte) 0x00); + buffer.put((byte) 0x08); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.put((byte) 0x80); + buffer.position(0); + buffer.limit(64); + assertStartsWith("Error while reading version", + assertThrows(MetadataParseException.class, + () -> serde.read(new ByteBufferAccessor(buffer), buffer.remaining())).getMessage()); + } + + /** + * Test attempting to parse an event which has a version > Short.MAX_VALUE + */ + @Test + public void testParsingVersionTooLarge() { + MetadataRecordSerde serde = new MetadataRecordSerde(); + ByteBuffer buffer = ByteBuffer.allocate(64); + buffer.clear(); + buffer.put((byte) 0x00); // frame version + buffer.put((byte) 0x08); // apiKey + buffer.put((byte) 0xff); // api version + buffer.put((byte) 0xff); // api version + buffer.put((byte) 0xff); // api version + buffer.put((byte) 0x7f); // api version end + buffer.put((byte) 0x80); + buffer.position(0); + buffer.limit(64); + assertStartsWith("Value for version was too large", + assertThrows(MetadataParseException.class, + () -> serde.read(new ByteBufferAccessor(buffer), buffer.remaining())).getMessage()); + } + + /** + * Test attempting to parse an event which has a unsupported version + */ + @Test + public void testParsingUnsupportedApiKey() { + MetadataRecordSerde serde = new MetadataRecordSerde(); + ByteBuffer buffer = ByteBuffer.allocate(64); + buffer.put((byte) 0x00); // frame version + buffer.put((byte) 0xff); // apiKey + buffer.put((byte) 0x7f); // apiKey + buffer.put((byte) 0x00); // api version + buffer.put((byte) 0x80); + buffer.position(0); + buffer.limit(64); + assertStartsWith("Unknown metadata id ", + assertThrows(MetadataParseException.class, + () -> serde.read(new ByteBufferAccessor(buffer), buffer.remaining())).getCause().getMessage()); + } + + /** + * Test attempting to parse an event which has a malformed message body. + */ + @Test + public void testParsingMalformedMessage() { + MetadataRecordSerde serde = new MetadataRecordSerde(); + ByteBuffer buffer = ByteBuffer.allocate(4); + buffer.put((byte) 0x00); // frame version + buffer.put((byte) 0x00); // apiKey + buffer.put((byte) 0x00); // apiVersion + buffer.put((byte) 0x80); // malformed data + buffer.position(0); + buffer.limit(4); + assertStartsWith("Failed to deserialize record with type", + assertThrows(MetadataParseException.class, + () -> serde.read(new ByteBufferAccessor(buffer), buffer.remaining())).getMessage()); + } + + /** + * Test attempting to parse an event which has a malformed message version varint. + */ + @Test + public void testParsingRecordWithGarbageAtEnd() { + MetadataRecordSerde serde = new MetadataRecordSerde(); + RegisterBrokerRecord message = new RegisterBrokerRecord().setBrokerId(1).setBrokerEpoch(2); + + ObjectSerializationCache cache = new ObjectSerializationCache(); + ApiMessageAndVersion messageAndVersion = new ApiMessageAndVersion(message, (short) 0); + int size = serde.recordSize(messageAndVersion, cache); + ByteBuffer buffer = ByteBuffer.allocate(size + 1); + + serde.write(messageAndVersion, cache, new ByteBufferAccessor(buffer)); + buffer.clear(); + assertStartsWith("Found 1 byte(s) of garbage after", + assertThrows(MetadataParseException.class, + () -> serde.read(new ByteBufferAccessor(buffer), size + 1)).getMessage()); + } + + private static void assertStartsWith(String prefix, String str) { + assertTrue(str.startsWith(prefix), + "Expected string '" + str + "' to start with '" + prefix + "'"); } } diff --git a/metadata/src/test/java/org/apache/kafka/metalog/LocalLogManagerTest.java b/metadata/src/test/java/org/apache/kafka/metalog/LocalLogManagerTest.java index 4d4e5101da994..7b5e26d79f6c7 100644 --- a/metadata/src/test/java/org/apache/kafka/metalog/LocalLogManagerTest.java +++ b/metadata/src/test/java/org/apache/kafka/metalog/LocalLogManagerTest.java @@ -96,7 +96,7 @@ public void testPassLeadership() throws Exception { private static void waitForLastCommittedOffset(long targetOffset, LocalLogManager logManager) throws InterruptedException { - TestUtils.retryOnExceptionWithTimeout(3, 20000, () -> { + TestUtils.retryOnExceptionWithTimeout(20000, 3, () -> { MockMetaLogManagerListener listener = (MockMetaLogManagerListener) logManager.listeners().get(0); long highestOffset = -1; diff --git a/metadata/src/test/java/org/apache/kafka/metalog/LocalLogManagerTestEnv.java b/metadata/src/test/java/org/apache/kafka/metalog/LocalLogManagerTestEnv.java index 9282f42237d66..4ff350e41a80a 100644 --- a/metadata/src/test/java/org/apache/kafka/metalog/LocalLogManagerTestEnv.java +++ b/metadata/src/test/java/org/apache/kafka/metalog/LocalLogManagerTestEnv.java @@ -108,7 +108,7 @@ File dir() { LeaderAndEpoch waitForLeader() throws InterruptedException { AtomicReference value = new AtomicReference<>(null); - TestUtils.retryOnExceptionWithTimeout(3, 20000, () -> { + TestUtils.retryOnExceptionWithTimeout(20000, 3, () -> { LeaderAndEpoch result = null; for (LocalLogManager logManager : logManagers) { LeaderAndEpoch leader = logManager.leaderAndEpoch(); diff --git a/raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java b/raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java index e0e7cb449357e..f004203339be8 100644 --- a/raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java +++ b/raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java @@ -309,17 +309,15 @@ private void updateListenersProgress(List listenerContexts, lon for (ListenerContext listenerContext : listenerContexts) { listenerContext.nextExpectedOffset().ifPresent(nextExpectedOffset -> { if (nextExpectedOffset < log.startOffset() && nextExpectedOffset < highWatermark) { - SnapshotReader snapshot = latestSnapshot().orElseThrow(() -> { - return new IllegalStateException( - String.format( - "Snapshot expected since next offset of %s is %s, log start offset is %s and high-watermark is %s", - listenerContext.listener.getClass().getTypeName(), - nextExpectedOffset, - log.startOffset(), - highWatermark - ) - ); - }); + SnapshotReader snapshot = latestSnapshot().orElseThrow(() -> new IllegalStateException( + String.format( + "Snapshot expected since next offset of %s is %s, log start offset is %s and high-watermark is %s", + listenerContext.listener.getClass().getTypeName(), + nextExpectedOffset, + log.startOffset(), + highWatermark + ) + )); listenerContext.fireHandleSnapshot(snapshot); } }); @@ -347,14 +345,10 @@ private Optional> latestSnapshot() { private void maybeFireHandleCommit(long baseOffset, int epoch, List records) { for (ListenerContext listenerContext : listenerContexts) { OptionalLong nextExpectedOffsetOpt = listenerContext.nextExpectedOffset(); - if (!nextExpectedOffsetOpt.isPresent()) { - continue; - } - - long nextExpectedOffset = nextExpectedOffsetOpt.getAsLong(); - if (nextExpectedOffset == baseOffset) { - listenerContext.fireHandleCommit(baseOffset, epoch, records); - } + nextExpectedOffsetOpt.ifPresent(nextOffset -> { + if (nextOffset == baseOffset) + listenerContext.fireHandleCommit(baseOffset, epoch, records); + }); } } @@ -388,7 +382,6 @@ public void initialize() { if (quorum.isVoter() && quorum.remoteVoters().isEmpty() && !quorum.isCandidate()) { - transitionToCandidate(currentTimeMs); } } catch (IOException e) { @@ -449,7 +442,7 @@ private void onBecomeLeader(long currentTimeMs) throws IOException { } private void flushLeaderLog(LeaderState state, long currentTimeMs) { - // We update the end offset before flushing so that parked fetches can return sooner + // We update the end offset before flushing so that parked fetches can return sooner. updateLeaderEndOffsetAndTimestamp(state, currentTimeMs); log.flush(); } @@ -502,11 +495,11 @@ private void onBecomeFollower(long currentTimeMs) { resetConnections(); // After becoming a follower, we need to complete all pending fetches so that - // they can be resent to the leader without waiting for their expiration + // they can be re-sent to the leader without waiting for their expirations fetchPurgatory.completeAllExceptionally(new NotLeaderOrFollowerException( "Cannot process the fetch request because the node is no longer the leader.")); - // Clearing the append purgatory should complete all future exceptionally since this node is no longer the leader + // Clearing the append purgatory should complete all futures exceptionally since this node is no longer the leader appendPurgatory.completeAllExceptionally(new NotLeaderOrFollowerException( "Failed to receive sufficient acknowledgments for this append before leader change.")); } @@ -552,7 +545,7 @@ private VoteResponseData handleVoteRequest( } if (!hasValidTopicPartition(request, log.topicPartition())) { - // Until we support multi-raft, we treat topic partition mismatches as invalid requests + // Until we support multi-raft, we treat individual topic partition mismatches as invalid requests return new VoteResponseData().setErrorCode(Errors.INVALID_REQUEST.code()); } @@ -638,7 +631,6 @@ private boolean handleVoteResponse( binaryExponentialElectionBackoffMs(state.retries()) ); } - } } else { logger.debug("Ignoring vote response {} since we are no longer a candidate in epoch {}", @@ -1072,7 +1064,7 @@ private boolean handleFetchResponse( FetchResponseData.EpochEndOffset divergingEpoch = partitionResponse.divergingEpoch(); if (divergingEpoch.epoch() >= 0) { // The leader is asking us to truncate before continuing - OffsetAndEpoch divergingOffsetAndEpoch = new OffsetAndEpoch( + final OffsetAndEpoch divergingOffsetAndEpoch = new OffsetAndEpoch( divergingEpoch.endOffset(), divergingEpoch.epoch()); state.highWatermark().ifPresent(highWatermark -> { @@ -1104,7 +1096,7 @@ private boolean handleFetchResponse( ); return false; } else { - OffsetAndEpoch snapshotId = new OffsetAndEpoch( + final OffsetAndEpoch snapshotId = new OffsetAndEpoch( partitionResponse.snapshotId().endOffset(), partitionResponse.snapshotId().epoch() ); @@ -1193,7 +1185,7 @@ private DescribeQuorumResponseData handleDescribeQuorumRequest( */ private FetchSnapshotResponseData handleFetchSnapshotRequest( RaftRequest.Inbound requestMetadata - ) throws IOException { + ) { FetchSnapshotRequestData data = (FetchSnapshotRequestData) requestMetadata.data; if (!hasValidClusterId(data.clusterId())) { @@ -2038,7 +2030,7 @@ private long pollFollowerAsObserver(FollowerState state, long currentTimeMs) thr } } - private long maybeSendFetchOrFetchSnapshot(FollowerState state, long currentTimeMs) throws IOException { + private long maybeSendFetchOrFetchSnapshot(FollowerState state, long currentTimeMs) { final Supplier requestSupplier; if (state.fetchingSnapshot().isPresent()) { @@ -2465,7 +2457,5 @@ public synchronized void onClose(BatchReader reader) { wakeup(); } } - } - } diff --git a/raft/src/test/java/org/apache/kafka/raft/RaftEventSimulationTest.java b/raft/src/test/java/org/apache/kafka/raft/RaftEventSimulationTest.java index d0016614b3892..1c48deeded4fe 100644 --- a/raft/src/test/java/org/apache/kafka/raft/RaftEventSimulationTest.java +++ b/raft/src/test/java/org/apache/kafka/raft/RaftEventSimulationTest.java @@ -18,6 +18,7 @@ import net.jqwik.api.ForAll; import net.jqwik.api.Property; +import net.jqwik.api.Tag; import net.jqwik.api.constraints.IntRange; import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.Uuid; @@ -37,7 +38,6 @@ import org.apache.kafka.raft.internals.BatchMemoryPool; import org.apache.kafka.server.common.serialization.RecordSerde; import org.apache.kafka.snapshot.SnapshotReader; -import org.junit.jupiter.api.Tag; import java.net.InetSocketAddress; import java.nio.ByteBuffer; diff --git a/release.py b/release.py index eacee7f2af437..1f45b7c3b90da 100755 --- a/release.py +++ b/release.py @@ -256,7 +256,7 @@ def command_stage_docs(): sys.exit("%s doesn't exist or does not appear to be the kafka-site repository" % kafka_site_repo_path) prefs = load_prefs() - jdk11_env = get_jdk(prefs, 11) + jdk15_env = get_jdk(prefs, 15) save_prefs(prefs) version = get_version() @@ -265,7 +265,7 @@ def command_stage_docs(): # version due to already having bumped the bugfix version number. gradle_version_override = docs_release_version(version) - cmd("Building docs", "./gradlew -Pversion=%s clean siteDocsTar aggregatedJavadoc" % gradle_version_override, cwd=REPO_HOME, env=jdk11_env) + cmd("Building docs", "./gradlew -Pversion=%s clean siteDocsTar aggregatedJavadoc" % gradle_version_override, cwd=REPO_HOME, env=jdk15_env) docs_tar = os.path.join(REPO_HOME, 'core', 'build', 'distributions', 'kafka_2.13-%s-site-docs.tgz' % gradle_version_override) @@ -426,7 +426,7 @@ def command_release_announcement_email(): if not user_ok("""Requirements: 1. Updated docs to reference the new release version where appropriate. -2. JDK8 and JDK11 compilers and libraries +2. JDK8 and JDK15 compilers and libraries 3. Your Apache ID, already configured with SSH keys on id.apache.org and SSH keys available in this shell session 4. All issues in the target release resolved with valid resolutions (if not, this script will report the problematic JIRAs) 5. A GPG key used for signing the release. This key should have been added to public Apache servers and the KEYS file on the Kafka site @@ -511,7 +511,7 @@ def command_release_announcement_email(): apache_id = get_pref(prefs, 'apache_id', lambda: raw_input("Enter your apache username: ")) jdk8_env = get_jdk(prefs, 8) -jdk11_env = get_jdk(prefs, 11) +jdk15_env = get_jdk(prefs, 15) def select_gpg_key(): print("Here are the available GPG keys:") @@ -600,7 +600,7 @@ def select_gpg_key(): cmd("Building artifacts", "./gradlew clean && ./gradlewAll releaseTarGz", cwd=kafka_dir, env=jdk8_env, shell=True) cmd("Copying artifacts", "cp %s/core/build/distributions/* %s" % (kafka_dir, artifacts_dir), shell=True) -cmd("Building docs", "./gradlew clean aggregatedJavadoc", cwd=kafka_dir, env=jdk11_env) +cmd("Building docs", "./gradlew clean aggregatedJavadoc", cwd=kafka_dir, env=jdk15_env) cmd("Copying docs", "cp -R %s/build/docs/javadoc %s" % (kafka_dir, artifacts_dir)) for filename in os.listdir(artifacts_dir): diff --git a/metadata/src/main/java/org/apache/kafka/queue/EventQueue.java b/server-common/src/main/java/org/apache/kafka/queue/EventQueue.java similarity index 100% rename from metadata/src/main/java/org/apache/kafka/queue/EventQueue.java rename to server-common/src/main/java/org/apache/kafka/queue/EventQueue.java diff --git a/metadata/src/main/java/org/apache/kafka/queue/KafkaEventQueue.java b/server-common/src/main/java/org/apache/kafka/queue/KafkaEventQueue.java similarity index 100% rename from metadata/src/main/java/org/apache/kafka/queue/KafkaEventQueue.java rename to server-common/src/main/java/org/apache/kafka/queue/KafkaEventQueue.java diff --git a/server-common/src/main/java/org/apache/kafka/server/common/serialization/AbstractApiMessageSerde.java b/server-common/src/main/java/org/apache/kafka/server/common/serialization/AbstractApiMessageSerde.java index 67c067d1a2b07..d292e390f3390 100644 --- a/server-common/src/main/java/org/apache/kafka/server/common/serialization/AbstractApiMessageSerde.java +++ b/server-common/src/main/java/org/apache/kafka/server/common/serialization/AbstractApiMessageSerde.java @@ -16,7 +16,6 @@ */ package org.apache.kafka.server.common.serialization; -import org.apache.kafka.common.errors.SerializationException; import org.apache.kafka.common.protocol.ApiMessage; import org.apache.kafka.common.protocol.ObjectSerializationCache; import org.apache.kafka.common.protocol.Readable; @@ -46,6 +45,19 @@ public abstract class AbstractApiMessageSerde implements RecordSerde Short.MAX_VALUE) { + throw new MetadataParseException("Value for " + entity + " was too large."); + } + return (short) val; + } + @Override public int recordSize(ApiMessageAndVersion data, ObjectSerializationCache serializationCache) { @@ -69,16 +81,30 @@ public void write(ApiMessageAndVersion data, @Override public ApiMessageAndVersion read(Readable input, int size) { - short frameVersion = (short) input.readUnsignedVarint(); + short frameVersion = unsignedIntToShort(input, "frame version"); + if (frameVersion != DEFAULT_FRAME_VERSION) { - throw new SerializationException("Could not deserialize metadata record due to unknown frame version " - + frameVersion + "(only frame version " + DEFAULT_FRAME_VERSION + " is supported)"); + throw new MetadataParseException("Could not deserialize metadata record due to unknown frame version " + + frameVersion + "(only frame version " + DEFAULT_FRAME_VERSION + " is supported)"); } + short apiKey = unsignedIntToShort(input, "type"); + short version = unsignedIntToShort(input, "version"); - short apiKey = (short) input.readUnsignedVarint(); - short version = (short) input.readUnsignedVarint(); - ApiMessage record = apiMessageFor(apiKey); - record.read(input, version); + ApiMessage record; + try { + record = apiMessageFor(apiKey); + } catch (Exception e) { + throw new MetadataParseException(e); + } + try { + record.read(input, version); + } catch (Exception e) { + throw new MetadataParseException("Failed to deserialize record with type " + apiKey, e); + } + if (input.remaining() > 0) { + throw new MetadataParseException("Found " + input.remaining() + + " byte(s) of garbage after " + apiKey); + } return new ApiMessageAndVersion(record, version); } diff --git a/metadata/src/main/java/org/apache/kafka/metadata/MetadataParseException.java b/server-common/src/main/java/org/apache/kafka/server/common/serialization/MetadataParseException.java similarity index 81% rename from metadata/src/main/java/org/apache/kafka/metadata/MetadataParseException.java rename to server-common/src/main/java/org/apache/kafka/server/common/serialization/MetadataParseException.java index 1c5d461776dbc..49eea2dc1ed61 100644 --- a/metadata/src/main/java/org/apache/kafka/metadata/MetadataParseException.java +++ b/server-common/src/main/java/org/apache/kafka/server/common/serialization/MetadataParseException.java @@ -14,8 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.apache.kafka.metadata; +package org.apache.kafka.server.common.serialization; /** * An exception indicating that we failed to parse a metadata entry. @@ -26,4 +25,12 @@ public class MetadataParseException extends RuntimeException { public MetadataParseException(String message) { super(message); } + + public MetadataParseException(Throwable e) { + super(e); + } + + public MetadataParseException(String message, Throwable throwable) { + super(message, throwable); + } } diff --git a/metadata/src/test/java/org/apache/kafka/queue/KafkaEventQueueTest.java b/server-common/src/test/java/org/apache/kafka/queue/KafkaEventQueueTest.java similarity index 100% rename from metadata/src/test/java/org/apache/kafka/queue/KafkaEventQueueTest.java rename to server-common/src/test/java/org/apache/kafka/queue/KafkaEventQueueTest.java diff --git a/streams/src/main/java/org/apache/kafka/streams/KafkaStreams.java b/streams/src/main/java/org/apache/kafka/streams/KafkaStreams.java index bf12927bda45c..a0628eac8f16a 100644 --- a/streams/src/main/java/org/apache/kafka/streams/KafkaStreams.java +++ b/streams/src/main/java/org/apache/kafka/streams/KafkaStreams.java @@ -44,7 +44,6 @@ import org.apache.kafka.streams.errors.StreamsException; import org.apache.kafka.streams.errors.StreamsNotStartedException; import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler; -import org.apache.kafka.streams.errors.TopologyException; import org.apache.kafka.streams.errors.UnknownStateStoreException; import org.apache.kafka.streams.errors.InvalidStateStorePartitionException; import org.apache.kafka.streams.internals.metrics.ClientMetrics; @@ -56,8 +55,7 @@ import org.apache.kafka.streams.processor.internals.ClientUtils; import org.apache.kafka.streams.processor.internals.DefaultKafkaClientSupplier; import org.apache.kafka.streams.processor.internals.GlobalStreamThread; -import org.apache.kafka.streams.processor.internals.InternalTopologyBuilder; -import org.apache.kafka.streams.processor.internals.ProcessorTopology; +import org.apache.kafka.streams.processor.internals.TopologyMetadata; import org.apache.kafka.streams.processor.internals.StateDirectory; import org.apache.kafka.streams.processor.internals.StreamThread; import org.apache.kafka.streams.processor.internals.StreamsMetadataState; @@ -163,8 +161,6 @@ public class KafkaStreams implements AutoCloseable { private final QueryableStoreProvider queryableStoreProvider; private final Admin adminClient; private final StreamsMetricsImpl streamsMetrics; - private final ProcessorTopology taskTopology; - private final ProcessorTopology globalTaskTopology; private final long totalCacheSize; private final StreamStateListener streamStateListener; private final StateRestoreListener delegatingStateRestoreListener; @@ -172,7 +168,7 @@ public class KafkaStreams implements AutoCloseable { private final ArrayList storeProviders; private final UUID processId; private final KafkaClientSupplier clientSupplier; - private final InternalTopologyBuilder internalTopologyBuilder; + protected final TopologyMetadata topologyMetadata; GlobalStreamThread globalStreamThread; private KafkaStreams.StateListener stateListener; @@ -694,7 +690,7 @@ public void onRestoreEnd(final TopicPartition topicPartition, final String store */ public KafkaStreams(final Topology topology, final Properties props) { - this(topology.internalTopologyBuilder, new StreamsConfig(props), new DefaultKafkaClientSupplier()); + this(topology, new StreamsConfig(props), new DefaultKafkaClientSupplier()); } /** @@ -712,7 +708,7 @@ public KafkaStreams(final Topology topology, public KafkaStreams(final Topology topology, final Properties props, final KafkaClientSupplier clientSupplier) { - this(topology.internalTopologyBuilder, new StreamsConfig(props), clientSupplier, Time.SYSTEM); + this(topology, new StreamsConfig(props), clientSupplier, Time.SYSTEM); } /** @@ -729,7 +725,7 @@ public KafkaStreams(final Topology topology, public KafkaStreams(final Topology topology, final Properties props, final Time time) { - this(topology.internalTopologyBuilder, new StreamsConfig(props), new DefaultKafkaClientSupplier(), time); + this(topology, new StreamsConfig(props), new DefaultKafkaClientSupplier(), time); } /** @@ -749,7 +745,7 @@ public KafkaStreams(final Topology topology, final Properties props, final KafkaClientSupplier clientSupplier, final Time time) { - this(topology.internalTopologyBuilder, new StreamsConfig(props), clientSupplier, time); + this(topology, new StreamsConfig(props), clientSupplier, time); } /** @@ -782,7 +778,7 @@ public KafkaStreams(final Topology topology, public KafkaStreams(final Topology topology, final StreamsConfig config, final KafkaClientSupplier clientSupplier) { - this(topology.internalTopologyBuilder, config, clientSupplier); + this(new TopologyMetadata(topology.internalTopologyBuilder, config), config, clientSupplier); } /** @@ -799,41 +795,41 @@ public KafkaStreams(final Topology topology, public KafkaStreams(final Topology topology, final StreamsConfig config, final Time time) { - this(topology.internalTopologyBuilder, config, new DefaultKafkaClientSupplier(), time); + this(new TopologyMetadata(topology.internalTopologyBuilder, config), config, new DefaultKafkaClientSupplier(), time); } - private KafkaStreams(final InternalTopologyBuilder internalTopologyBuilder, - final StreamsConfig config, - final KafkaClientSupplier clientSupplier) throws StreamsException { - this(internalTopologyBuilder, config, clientSupplier, Time.SYSTEM); - } - - private KafkaStreams(final InternalTopologyBuilder internalTopologyBuilder, + private KafkaStreams(final Topology topology, final StreamsConfig config, final KafkaClientSupplier clientSupplier, final Time time) throws StreamsException { + this(new TopologyMetadata(topology.internalTopologyBuilder, config), config, clientSupplier, time); + } + + protected KafkaStreams(final TopologyMetadata topologyMetadata, + final StreamsConfig config, + final KafkaClientSupplier clientSupplier) throws StreamsException { + this(topologyMetadata, config, clientSupplier, Time.SYSTEM); + } + + protected KafkaStreams(final TopologyMetadata topologyMetadata, + final StreamsConfig config, + final KafkaClientSupplier clientSupplier, + final Time time) throws StreamsException { this.config = config; this.time = time; - this.internalTopologyBuilder = internalTopologyBuilder; - internalTopologyBuilder.rewriteTopology(config); - - // sanity check to fail-fast in case we cannot build a ProcessorTopology due to an exception - taskTopology = internalTopologyBuilder.buildTopology(); - globalTaskTopology = internalTopologyBuilder.buildGlobalStateTopology(); + this.topologyMetadata = topologyMetadata; + this.topologyMetadata.buildAndRewriteTopology(); - final boolean hasGlobalTopology = globalTaskTopology != null; - final boolean hasPersistentStores = taskTopology.hasPersistentLocalStore() || - (hasGlobalTopology && globalTaskTopology.hasPersistentGlobalStore()); + final boolean hasGlobalTopology = topologyMetadata.hasGlobalTopology(); try { - stateDirectory = new StateDirectory(config, time, hasPersistentStores, internalTopologyBuilder.hasNamedTopologies()); + stateDirectory = new StateDirectory(config, time, topologyMetadata.hasPersistentStores(), topologyMetadata.hasNamedTopologies()); processId = stateDirectory.initializeProcessId(); } catch (final ProcessorStateException fatal) { throw new StreamsException(fatal); } - // The application ID is a required config and hence should always have value final String userClientId = config.getString(StreamsConfig.CLIENT_ID_CONFIG); final String applicationId = config.getString(StreamsConfig.APPLICATION_ID_CONFIG); @@ -863,12 +859,12 @@ private KafkaStreams(final InternalTopologyBuilder internalTopologyBuilder, ClientMetrics.addVersionMetric(streamsMetrics); ClientMetrics.addCommitIdMetric(streamsMetrics); ClientMetrics.addApplicationIdMetric(streamsMetrics, config.getString(StreamsConfig.APPLICATION_ID_CONFIG)); - ClientMetrics.addTopologyDescriptionMetric(streamsMetrics, internalTopologyBuilder.describe().toString()); + ClientMetrics.addTopologyDescriptionMetric(streamsMetrics, this.topologyMetadata.topologyDescription()); ClientMetrics.addStateMetric(streamsMetrics, (metricsConfig, now) -> state); ClientMetrics.addNumAliveStreamThreadMetric(streamsMetrics, (metricsConfig, now) -> getNumLiveStreamThreads()); streamsMetadataState = new StreamsMetadataState( - internalTopologyBuilder, + this.topologyMetadata, parseHostInfo(config.getString(StreamsConfig.APPLICATION_SERVER_CONFIG))); oldHandler = false; @@ -876,14 +872,14 @@ private KafkaStreams(final InternalTopologyBuilder internalTopologyBuilder, delegatingStateRestoreListener = new DelegatingStateRestoreListener(); totalCacheSize = config.getLong(StreamsConfig.CACHE_MAX_BYTES_BUFFERING_CONFIG); - final int numStreamThreads = getNumStreamThreads(hasGlobalTopology); + final int numStreamThreads = topologyMetadata.getNumStreamThreads(config); final long cacheSizePerThread = getCacheSizePerThread(numStreamThreads); GlobalStreamThread.State globalThreadState = null; if (hasGlobalTopology) { final String globalThreadId = clientId + "-GlobalStreamThread"; globalStreamThread = new GlobalStreamThread( - globalTaskTopology, + topologyMetadata.globalTaskTopology(), config, clientSupplier.getGlobalConsumer(config.getGlobalConsumerConfigs(clientId)), stateDirectory, @@ -901,7 +897,7 @@ private KafkaStreams(final InternalTopologyBuilder internalTopologyBuilder, threadState = new HashMap<>(numStreamThreads); streamStateListener = new StreamStateListener(threadState, globalThreadState); - final GlobalStateStoreProvider globalStateStoreProvider = new GlobalStateStoreProvider(internalTopologyBuilder.globalStateStores()); + final GlobalStateStoreProvider globalStateStoreProvider = new GlobalStateStoreProvider(this.topologyMetadata.globalStateStores()); if (hasGlobalTopology) { globalStreamThread.setStateListener(streamStateListener); @@ -920,7 +916,7 @@ private KafkaStreams(final InternalTopologyBuilder internalTopologyBuilder, private StreamThread createAndAddStreamThread(final long cacheSizePerThread, final int threadIdx) { final StreamThread streamThread = StreamThread.create( - internalTopologyBuilder, + topologyMetadata, config, clientSupplier, adminClient, @@ -959,23 +955,6 @@ private static Metrics getMetrics(final StreamsConfig config, final Time time, f return new Metrics(metricConfig, reporters, time, metricsContext); } - private int getNumStreamThreads(final boolean hasGlobalTopology) { - final int numStreamThreads; - if (internalTopologyBuilder.hasNoNonGlobalTopology()) { - log.info("Overriding number of StreamThreads to zero for global-only topology"); - numStreamThreads = 0; - } else { - numStreamThreads = config.getInt(StreamsConfig.NUM_STREAM_THREADS_CONFIG); - } - - if (numStreamThreads == 0 && !hasGlobalTopology) { - log.error("Topology with no input topics will create no stream threads and no global thread."); - throw new TopologyException("Topology has no stream threads and no global threads, " + - "must subscribe to at least one source topic or global table."); - } - return numStreamThreads; - } - /** * Adds and starts a stream thread in addition to the stream threads that are already running in this * Kafka Streams client. @@ -1200,7 +1179,7 @@ private long getCacheSizePerThread(final int numStreamThreads) { if (numStreamThreads == 0) { return totalCacheSize; } - return totalCacheSize / (numStreamThreads + ((globalTaskTopology != null) ? 1 : 0)); + return totalCacheSize / (numStreamThreads + (topologyMetadata.hasGlobalTopology() ? 1 : 0)); } private void resizeThreadCache(final long cacheSizePerThread) { @@ -1296,6 +1275,14 @@ public synchronized void start() throws IllegalStateException, StreamsException } else { throw new IllegalStateException("The client is either already started or already stopped, cannot re-start"); } + + if (topologyMetadata.isEmpty()) { + if (setState(State.RUNNING)) { + log.debug("Transitioning directly to RUNNING for app with no named topologies"); + } else { + throw new IllegalStateException("Unexpected error in transitioning empty KafkaStreams to RUNNING"); + } + } } /** @@ -1537,8 +1524,7 @@ public KeyQueryMetadata queryMetadataForKey(final String storeName, public T store(final StoreQueryParameters storeQueryParameters) { validateIsRunningOrRebalancing(); final String storeName = storeQueryParameters.storeName(); - if ((taskTopology == null || !taskTopology.hasStore(storeName)) - && (globalTaskTopology == null || !globalTaskTopology.hasStore(storeName))) { + if (!topologyMetadata.hasStore(storeName)) { throw new UnknownStateStoreException( "Cannot get state store " + storeName + " because no such store is registered in the topology." ); diff --git a/streams/src/main/java/org/apache/kafka/streams/StreamsConfig.java b/streams/src/main/java/org/apache/kafka/streams/StreamsConfig.java index 66281eb61fbd5..2ef29af8b552f 100644 --- a/streams/src/main/java/org/apache/kafka/streams/StreamsConfig.java +++ b/streams/src/main/java/org/apache/kafka/streams/StreamsConfig.java @@ -324,11 +324,6 @@ public class StreamsConfig extends AbstractConfig { @SuppressWarnings("WeakerAccess") public static final String EXACTLY_ONCE_V2 = "exactly_once_v2"; - /** - * Config value for parameter {@link #BUILT_IN_METRICS_VERSION_CONFIG "built.in.metrics.version"} for built-in metrics from version 0.10.0. to 2.4 - */ - public static final String METRICS_0100_TO_24 = "0.10.0-2.4"; - /** * Config value for parameter {@link #BUILT_IN_METRICS_VERSION_CONFIG "built.in.metrics.version"} for the latest built-in metrics version. */ @@ -753,7 +748,6 @@ public class StreamsConfig extends AbstractConfig { Type.STRING, METRICS_LATEST, in( - METRICS_0100_TO_24, METRICS_LATEST ), Importance.LOW, diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/Suppressed.java b/streams/src/main/java/org/apache/kafka/streams/kstream/Suppressed.java index 2f96993719f3d..31a53cec8f69a 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/Suppressed.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/Suppressed.java @@ -22,6 +22,7 @@ import org.apache.kafka.streams.kstream.internals.suppress.SuppressedInternal; import java.time.Duration; +import java.util.Collections; import java.util.Map; public interface Suppressed extends NamedOperation> { @@ -48,7 +49,7 @@ interface BufferConfig> { * Create a size-constrained buffer in terms of the maximum number of keys it will store. */ static EagerBufferConfig maxRecords(final long recordLimit) { - return new EagerBufferConfigImpl(recordLimit, Long.MAX_VALUE); + return new EagerBufferConfigImpl(recordLimit, Long.MAX_VALUE, Collections.emptyMap()); } /** @@ -60,7 +61,7 @@ static EagerBufferConfig maxRecords(final long recordLimit) { * Create a size-constrained buffer in terms of the maximum number of bytes it will use. */ static EagerBufferConfig maxBytes(final long byteLimit) { - return new EagerBufferConfigImpl(Long.MAX_VALUE, byteLimit); + return new EagerBufferConfigImpl(Long.MAX_VALUE, byteLimit, Collections.emptyMap()); } /** diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamAggregate.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamAggregate.java index dec197361db34..4397fbc429d55 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamAggregate.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamAggregate.java @@ -28,7 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor; +import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensor; import static org.apache.kafka.streams.state.ValueAndTimestamp.getValueOrNull; public class KStreamAggregate implements KStreamAggProcessorSupplier { @@ -66,7 +66,7 @@ private class KStreamAggregateProcessor extends AbstractProcessor { @Override public void init(final ProcessorContext context) { super.init(context); - droppedRecordsSensor = droppedRecordsSensorOrSkippedRecordsSensor( + droppedRecordsSensor = droppedRecordsSensor( Thread.currentThread().getName(), context.taskId().toString(), (StreamsMetricsImpl) context.metrics()); diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamKStreamJoin.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamKStreamJoin.java index 14d0bdb97d17f..2788bacb62bd1 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamKStreamJoin.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamKStreamJoin.java @@ -38,7 +38,7 @@ import java.util.Optional; import static org.apache.kafka.streams.StreamsConfig.InternalConfig.ENABLE_KSTREAMS_OUTER_JOIN_SPURIOUS_RESULTS_FIX; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor; +import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensor; class KStreamKStreamJoin implements ProcessorSupplier { private static final Logger LOG = LoggerFactory.getLogger(KStreamKStreamJoin.class); @@ -91,7 +91,7 @@ private class KStreamKStreamJoinProcessor extends AbstractProcessor { public void init(final ProcessorContext context) { super.init(context); metrics = (StreamsMetricsImpl) context.metrics(); - droppedRecordsSensor = droppedRecordsSensorOrSkippedRecordsSensor(Thread.currentThread().getName(), context.taskId().toString(), metrics); + droppedRecordsSensor = droppedRecordsSensor(Thread.currentThread().getName(), context.taskId().toString(), metrics); otherWindowStore = context.getStateStore(otherWindowName); if (StreamsConfig.InternalConfig.getBoolean(context().appConfigs(), ENABLE_KSTREAMS_OUTER_JOIN_SPURIOUS_RESULTS_FIX, true)) { diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamKTableJoinProcessor.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamKTableJoinProcessor.java index 5cf31424b8fb2..a781c4abd2f3d 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamKTableJoinProcessor.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamKTableJoinProcessor.java @@ -25,7 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor; +import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensor; import static org.apache.kafka.streams.state.ValueAndTimestamp.getValueOrNull; class KStreamKTableJoinProcessor extends AbstractProcessor { @@ -52,7 +52,7 @@ class KStreamKTableJoinProcessor extends AbstractProcessor implements KStreamAggProcessorSupplier { @@ -62,7 +62,7 @@ private class KStreamReduceProcessor extends AbstractProcessor { @Override public void init(final ProcessorContext context) { super.init(context); - droppedRecordsSensor = droppedRecordsSensorOrSkippedRecordsSensor( + droppedRecordsSensor = droppedRecordsSensor( Thread.currentThread().getName(), context.taskId().toString(), (StreamsMetricsImpl) context.metrics() diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamSessionWindowAggregate.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamSessionWindowAggregate.java index 39869e331bb78..413d6ca1098f5 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamSessionWindowAggregate.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamSessionWindowAggregate.java @@ -27,7 +27,6 @@ import org.apache.kafka.streams.processor.AbstractProcessor; import org.apache.kafka.streams.processor.Processor; import org.apache.kafka.streams.processor.ProcessorContext; -import org.apache.kafka.streams.processor.internals.InternalProcessorContext; import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl; import org.apache.kafka.streams.state.KeyValueIterator; import org.apache.kafka.streams.state.SessionStore; @@ -38,8 +37,7 @@ import java.util.ArrayList; import java.util.List; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrLateRecordDropSensor; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor; +import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensor; public class KStreamSessionWindowAggregate implements KStreamAggProcessorSupplier, V, Agg> { private static final Logger LOG = LoggerFactory.getLogger(KStreamSessionWindowAggregate.class); @@ -91,13 +89,12 @@ public void init(final ProcessorContext context) { super.init(context); final StreamsMetricsImpl metrics = (StreamsMetricsImpl) context.metrics(); final String threadId = Thread.currentThread().getName(); - lateRecordDropSensor = droppedRecordsSensorOrLateRecordDropSensor( + lateRecordDropSensor = droppedRecordsSensor( threadId, context.taskId().toString(), - ((InternalProcessorContext) context).currentNode().name(), metrics ); - droppedRecordsSensor = droppedRecordsSensorOrSkippedRecordsSensor(threadId, context.taskId().toString(), metrics); + droppedRecordsSensor = droppedRecordsSensor(threadId, context.taskId().toString(), metrics); store = context.getStateStore(storeName); tupleForwarder = new SessionTupleForwarder<>(store, context, new SessionCacheFlushListener<>(context), sendOldValues); } diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamSlidingWindowAggregate.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamSlidingWindowAggregate.java index 523273f9a5173..c3ae7ceae0f48 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamSlidingWindowAggregate.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamSlidingWindowAggregate.java @@ -37,8 +37,7 @@ import java.util.HashSet; import java.util.Set; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrLateRecordDropSensor; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor; +import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensor; import static org.apache.kafka.streams.state.ValueAndTimestamp.getValueOrNull; public class KStreamSlidingWindowAggregate implements KStreamAggProcessorSupplier, V, Agg> { @@ -89,13 +88,12 @@ public void init(final ProcessorContext context) { final InternalProcessorContext internalProcessorContext = (InternalProcessorContext) context; final StreamsMetricsImpl metrics = internalProcessorContext.metrics(); final String threadId = Thread.currentThread().getName(); - lateRecordDropSensor = droppedRecordsSensorOrLateRecordDropSensor( + lateRecordDropSensor = droppedRecordsSensor( threadId, context.taskId().toString(), - internalProcessorContext.currentNode().name(), metrics ); - droppedRecordsSensor = droppedRecordsSensorOrSkippedRecordsSensor( + droppedRecordsSensor = droppedRecordsSensor( threadId, context.taskId().toString(), metrics diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamWindowAggregate.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamWindowAggregate.java index 94a278e9b2699..300f3872e4d3f 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamWindowAggregate.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KStreamWindowAggregate.java @@ -35,8 +35,7 @@ import java.util.Map; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrLateRecordDropSensor; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor; +import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensor; import static org.apache.kafka.streams.state.ValueAndTimestamp.getValueOrNull; public class KStreamWindowAggregate implements KStreamAggProcessorSupplier, V, Agg> { @@ -87,13 +86,12 @@ public void init(final ProcessorContext context) { final InternalProcessorContext internalProcessorContext = (InternalProcessorContext) context; final StreamsMetricsImpl metrics = internalProcessorContext.metrics(); final String threadId = Thread.currentThread().getName(); - lateRecordDropSensor = droppedRecordsSensorOrLateRecordDropSensor( + lateRecordDropSensor = droppedRecordsSensor( threadId, context.taskId().toString(), - internalProcessorContext.currentNode().name(), metrics ); - droppedRecordsSensor = droppedRecordsSensorOrSkippedRecordsSensor( + droppedRecordsSensor = droppedRecordsSensor( threadId, context.taskId().toString(), metrics diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableFilter.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableFilter.java index 76753a40b69c6..3d974085d075b 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableFilter.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableFilter.java @@ -17,23 +17,23 @@ package org.apache.kafka.streams.kstream.internals; import org.apache.kafka.streams.kstream.Predicate; -import org.apache.kafka.streams.processor.AbstractProcessor; -import org.apache.kafka.streams.processor.Processor; -import org.apache.kafka.streams.processor.ProcessorContext; +import org.apache.kafka.streams.processor.api.Processor; +import org.apache.kafka.streams.processor.api.ProcessorContext; +import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.state.TimestampedKeyValueStore; import org.apache.kafka.streams.state.ValueAndTimestamp; import static org.apache.kafka.streams.state.ValueAndTimestamp.getValueOrNull; -class KTableFilter implements KTableProcessorSupplier { - private final KTableImpl parent; - private final Predicate predicate; +class KTableFilter implements KTableNewProcessorSupplier { + private final KTableImpl parent; + private final Predicate predicate; private final boolean filterNot; private final String queryableName; private boolean sendOldValues; - KTableFilter(final KTableImpl parent, - final Predicate predicate, + KTableFilter(final KTableImpl parent, + final Predicate predicate, final boolean filterNot, final String queryableName) { this.parent = parent; @@ -45,7 +45,7 @@ class KTableFilter implements KTableProcessorSupplier { } @Override - public Processor> get() { + public Processor, KIn, Change> get() { return new KTableFilterProcessor(); } @@ -62,8 +62,8 @@ public boolean enableSendingOldValues(final boolean forceMaterialization) { return sendOldValues; } - private V computeValue(final K key, final V value) { - V newValue = null; + private VIn computeValue(final KIn key, final VIn value) { + VIn newValue = null; if (value != null && (filterNot ^ predicate.test(key, value))) { newValue = value; @@ -72,11 +72,11 @@ private V computeValue(final K key, final V value) { return newValue; } - private ValueAndTimestamp computeValue(final K key, final ValueAndTimestamp valueAndTimestamp) { - ValueAndTimestamp newValueAndTimestamp = null; + private ValueAndTimestamp computeValue(final KIn key, final ValueAndTimestamp valueAndTimestamp) { + ValueAndTimestamp newValueAndTimestamp = null; if (valueAndTimestamp != null) { - final V value = valueAndTimestamp.value(); + final VIn value = valueAndTimestamp.value(); if (filterNot ^ predicate.test(key, value)) { newValueAndTimestamp = valueAndTimestamp; } @@ -86,13 +86,14 @@ private ValueAndTimestamp computeValue(final K key, final ValueAndTimestamp> { - private TimestampedKeyValueStore store; - private TimestampedTupleForwarder tupleForwarder; + private class KTableFilterProcessor implements Processor, KIn, Change> { + private ProcessorContext> context; + private TimestampedKeyValueStore store; + private TimestampedTupleForwarder tupleForwarder; @Override - public void init(final ProcessorContext context) { - super.init(context); + public void init(final ProcessorContext> context) { + this.context = context; if (queryableName != null) { store = context.getStateStore(queryableName); tupleForwarder = new TimestampedTupleForwarder<>( @@ -104,23 +105,26 @@ public void init(final ProcessorContext context) { } @Override - public void process(final K key, final Change change) { - final V newValue = computeValue(key, change.newValue); - final V oldValue = computeOldValue(key, change); + public void process(final Record> record) { + final KIn key = record.key(); + final Change change = record.value(); + + final VIn newValue = computeValue(key, change.newValue); + final VIn oldValue = computeOldValue(key, change); if (sendOldValues && oldValue == null && newValue == null) { return; // unnecessary to forward here. } if (queryableName != null) { - store.put(key, ValueAndTimestamp.make(newValue, context().timestamp())); - tupleForwarder.maybeForward(key, newValue, oldValue); + store.put(key, ValueAndTimestamp.make(newValue, record.timestamp())); + tupleForwarder.maybeForward(record.withValue(new Change<>(newValue, oldValue))); } else { - context().forward(key, new Change<>(newValue, oldValue)); + context.forward(record.withValue(new Change<>(newValue, oldValue))); } } - private V computeOldValue(final K key, final Change change) { + private VIn computeOldValue(final KIn key, final Change change) { if (!sendOldValues) { return null; } @@ -132,16 +136,16 @@ private V computeOldValue(final K key, final Change change) { } @Override - public KTableValueGetterSupplier view() { + public KTableValueGetterSupplier view() { // if the KTable is materialized, use the materialized store to return getter value; // otherwise rely on the parent getter and apply filter on-the-fly if (queryableName != null) { return new KTableMaterializedValueGetterSupplier<>(queryableName); } else { - return new KTableValueGetterSupplier() { - final KTableValueGetterSupplier parentValueGetterSupplier = parent.valueGetterSupplier(); + return new KTableValueGetterSupplier() { + final KTableValueGetterSupplier parentValueGetterSupplier = parent.valueGetterSupplier(); - public KTableValueGetter get() { + public KTableValueGetter get() { return new KTableFilterValueGetter(parentValueGetterSupplier.get()); } @@ -154,20 +158,22 @@ public String[] storeNames() { } - private class KTableFilterValueGetter implements KTableValueGetter { - private final KTableValueGetter parentGetter; + private class KTableFilterValueGetter implements KTableValueGetter { + private final KTableValueGetter parentGetter; - KTableFilterValueGetter(final KTableValueGetter parentGetter) { + KTableFilterValueGetter(final KTableValueGetter parentGetter) { this.parentGetter = parentGetter; } @Override - public void init(final ProcessorContext context) { + public void init(final org.apache.kafka.streams.processor.ProcessorContext context) { + // This is the old processor context for compatibility with the other KTable processors. + // Once we migrte them all, we can swap this out. parentGetter.init(context); } @Override - public ValueAndTimestamp get(final K key) { + public ValueAndTimestamp get(final KIn key) { return computeValue(key, parentGetter.get(key)); } diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableImpl.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableImpl.java index 52f7b5f02f2b3..faee7af4ce401 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableImpl.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableImpl.java @@ -124,7 +124,10 @@ public class KTableImpl extends AbstractStream implements KTable< private static final String TOPIC_SUFFIX = "-topic"; private static final String SINK_NAME = "KTABLE-SINK-"; - private final ProcessorSupplier processorSupplier; + // Temporarily setting the processorSupplier to type Object so that we can transition from the + // old ProcessorSupplier to the new api.ProcessorSupplier. This works because all accesses to + // this field are guarded by typechecks anyway. + private final Object processorSupplier; private final String queryableStoreName; @@ -143,6 +146,19 @@ public KTableImpl(final String name, this.queryableStoreName = queryableStoreName; } + public KTableImpl(final String name, + final Serde keySerde, + final Serde valueSerde, + final Set subTopologySourceNodes, + final String queryableStoreName, + final org.apache.kafka.streams.processor.api.ProcessorSupplier newProcessorSupplier, + final GraphNode graphNode, + final InternalStreamsBuilder builder) { + super(name, keySerde, valueSerde, subTopologySourceNodes, graphNode, builder); + this.processorSupplier = newProcessorSupplier; + this.queryableStoreName = queryableStoreName; + } + @Override public String queryableStoreName() { return queryableStoreName; @@ -179,7 +195,7 @@ private KTable doFilter(final Predicate predicate, } final String name = new NamedInternal(named).orElseGenerateWithPrefix(builder, FILTER_NAME); - final KTableProcessorSupplier processorSupplier = + final KTableNewProcessorSupplier processorSupplier = new KTableFilter<>(this, predicate, filterNot, queryableStoreName); final ProcessorParameters processorParameters = unsafeCastProcessorParametersToCompletelyDifferentType( @@ -194,7 +210,7 @@ private KTable doFilter(final Predicate predicate, builder.addGraphNode(this.graphNode, tableNode); - return new KTableImpl<>( + return new KTableImpl( name, keySerde, valueSerde, @@ -816,6 +832,8 @@ public KTableValueGetterSupplier valueGetterSupplier() { return new KTableSourceValueGetterSupplier<>(source.queryableName()); } else if (processorSupplier instanceof KStreamAggProcessorSupplier) { return ((KStreamAggProcessorSupplier) processorSupplier).view(); + } else if (processorSupplier instanceof KTableNewProcessorSupplier) { + return ((KTableNewProcessorSupplier) processorSupplier).view(); } else { return ((KTableProcessorSupplier) processorSupplier).view(); } @@ -832,6 +850,12 @@ public boolean enableSendingOldValues(final boolean forceMaterialization) { source.enableSendingOldValues(); } else if (processorSupplier instanceof KStreamAggProcessorSupplier) { ((KStreamAggProcessorSupplier) processorSupplier).enableSendingOldValues(); + } else if (processorSupplier instanceof KTableNewProcessorSupplier) { + final KTableNewProcessorSupplier tableProcessorSupplier = + (KTableNewProcessorSupplier) processorSupplier; + if (!tableProcessorSupplier.enableSendingOldValues(forceMaterialization)) { + return false; + } } else { final KTableProcessorSupplier tableProcessorSupplier = (KTableProcessorSupplier) processorSupplier; if (!tableProcessorSupplier.enableSendingOldValues(forceMaterialization)) { diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableKTableInnerJoin.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableKTableInnerJoin.java index f7f99929aa4b1..4626c365dea6c 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableKTableInnerJoin.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableKTableInnerJoin.java @@ -28,7 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor; +import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensor; import static org.apache.kafka.streams.state.ValueAndTimestamp.getValueOrNull; class KTableKTableInnerJoin extends KTableKTableAbstractJoin { @@ -76,7 +76,7 @@ private class KTableKTableJoinProcessor extends AbstractProcessor> @Override public void init(final ProcessorContext context) { super.init(context); - droppedRecordsSensor = droppedRecordsSensorOrSkippedRecordsSensor( + droppedRecordsSensor = droppedRecordsSensor( Thread.currentThread().getName(), context.taskId().toString(), (StreamsMetricsImpl) context.metrics() diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableKTableLeftJoin.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableKTableLeftJoin.java index b33e29894d483..abc28c0a477b3 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableKTableLeftJoin.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableKTableLeftJoin.java @@ -27,7 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor; +import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensor; import static org.apache.kafka.streams.processor.internals.RecordQueue.UNKNOWN; import static org.apache.kafka.streams.state.ValueAndTimestamp.getValueOrNull; @@ -75,7 +75,7 @@ private class KTableKTableLeftJoinProcessor extends AbstractProcessor extends KTableKTableAbstractJoin { @@ -73,7 +73,7 @@ private class KTableKTableRightJoinProcessor extends AbstractProcessor extends ProcessorSupplier, KOut, Change> { + + KTableValueGetterSupplier view(); + + /** + * Potentially enables sending old values. + *

        + * If {@code forceMaterialization} is {@code true}, the method will force the materialization of upstream nodes to + * enable sending old values. + *

        + * If {@code forceMaterialization} is {@code false}, the method will only enable the sending of old values if + * an upstream node is already materialized. + * + * @param forceMaterialization indicates if an upstream node should be forced to materialize to enable sending old + * values. + * @return {@code true} if sending old values is enabled, i.e. either because {@code forceMaterialization} was + * {@code true} or some upstream node is materialized. + */ + boolean enableSendingOldValues(boolean forceMaterialization); +} diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableSource.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableSource.java index b9f3580234a15..baae243b1aa83 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableSource.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/KTableSource.java @@ -29,7 +29,7 @@ import java.util.Objects; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor; +import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensor; public class KTableSource implements ProcessorSupplier { private static final Logger LOG = LoggerFactory.getLogger(KTableSource.class); @@ -84,7 +84,7 @@ private class KTableSourceProcessor extends AbstractProcessor { public void init(final ProcessorContext context) { super.init(context); metrics = (StreamsMetricsImpl) context.metrics(); - droppedRecordsSensor = droppedRecordsSensorOrSkippedRecordsSensor(Thread.currentThread().getName(), context.taskId().toString(), metrics); + droppedRecordsSensor = droppedRecordsSensor(Thread.currentThread().getName(), context.taskId().toString(), metrics); if (queryableName != null) { store = (TimestampedKeyValueStore) context.getStateStore(queryableName); tupleForwarder = new TimestampedTupleForwarder<>( diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/SessionCacheFlushListener.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/SessionCacheFlushListener.java index f40fdfe70ea6f..daa7c647512d1 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/SessionCacheFlushListener.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/SessionCacheFlushListener.java @@ -19,25 +19,29 @@ import org.apache.kafka.streams.kstream.Windowed; import org.apache.kafka.streams.processor.ProcessorContext; import org.apache.kafka.streams.processor.To; +import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.processor.internals.InternalProcessorContext; import org.apache.kafka.streams.processor.internals.ProcessorNode; import org.apache.kafka.streams.state.internals.CacheFlushListener; -class SessionCacheFlushListener implements CacheFlushListener, V> { - private final InternalProcessorContext context; +class SessionCacheFlushListener implements CacheFlushListener, VOut> { + private final InternalProcessorContext, Change> context; + + @SuppressWarnings("rawtypes") private final ProcessorNode myNode; + @SuppressWarnings("unchecked") SessionCacheFlushListener(final ProcessorContext context) { - this.context = (InternalProcessorContext) context; + this.context = (InternalProcessorContext, Change>) context; myNode = this.context.currentNode(); } @Override - public void apply(final Windowed key, - final V newValue, - final V oldValue, + public void apply(final Windowed key, + final VOut newValue, + final VOut oldValue, final long timestamp) { - final ProcessorNode prev = context.currentNode(); + @SuppressWarnings("rawtypes") final ProcessorNode prev = context.currentNode(); context.setCurrentNode(myNode); try { context.forward(key, new Change<>(newValue, oldValue), To.all().withTimestamp(key.window().end())); @@ -45,4 +49,15 @@ public void apply(final Windowed key, context.setCurrentNode(prev); } } + + @Override + public void apply(final Record, Change> record) { + @SuppressWarnings("rawtypes") final ProcessorNode prev = context.currentNode(); + context.setCurrentNode(myNode); + try { + context.forward(record.withTimestamp(record.key().window().end())); + } finally { + context.setCurrentNode(prev); + } + } } diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/TimestampedCacheFlushListener.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/TimestampedCacheFlushListener.java index 5540376d0cb8c..4034414cc1003 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/TimestampedCacheFlushListener.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/TimestampedCacheFlushListener.java @@ -16,8 +16,9 @@ */ package org.apache.kafka.streams.kstream.internals; -import org.apache.kafka.streams.processor.ProcessorContext; import org.apache.kafka.streams.processor.To; +import org.apache.kafka.streams.processor.api.ProcessorContext; +import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.processor.internals.InternalProcessorContext; import org.apache.kafka.streams.processor.internals.ProcessorNode; import org.apache.kafka.streams.state.ValueAndTimestamp; @@ -25,19 +26,27 @@ import static org.apache.kafka.streams.state.ValueAndTimestamp.getValueOrNull; -class TimestampedCacheFlushListener implements CacheFlushListener> { - private final InternalProcessorContext context; +class TimestampedCacheFlushListener implements CacheFlushListener> { + private final InternalProcessorContext> context; + + @SuppressWarnings("rawtypes") private final ProcessorNode myNode; - TimestampedCacheFlushListener(final ProcessorContext context) { - this.context = (InternalProcessorContext) context; + TimestampedCacheFlushListener(final ProcessorContext> context) { + this.context = (InternalProcessorContext>) context; + myNode = this.context.currentNode(); + } + + @SuppressWarnings("unchecked") + TimestampedCacheFlushListener(final org.apache.kafka.streams.processor.ProcessorContext context) { + this.context = (InternalProcessorContext>) context; myNode = this.context.currentNode(); } @Override - public void apply(final K key, - final ValueAndTimestamp newValue, - final ValueAndTimestamp oldValue, + public void apply(final KOut key, + final ValueAndTimestamp newValue, + final ValueAndTimestamp oldValue, final long timestamp) { final ProcessorNode prev = context.currentNode(); context.setCurrentNode(myNode); @@ -50,4 +59,22 @@ public void apply(final K key, context.setCurrentNode(prev); } } + + @Override + public void apply(final Record>> record) { + @SuppressWarnings("rawtypes") final ProcessorNode prev = context.currentNode(); + context.setCurrentNode(myNode); + try { + context.forward( + record.withValue( + new Change<>( + getValueOrNull(record.value().newValue), + getValueOrNull(record.value().oldValue) + ) + ) + ); + } finally { + context.setCurrentNode(prev); + } + } } diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/TimestampedTupleForwarder.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/TimestampedTupleForwarder.java index 910dd8fffbf25..6411b35511019 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/TimestampedTupleForwarder.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/TimestampedTupleForwarder.java @@ -16,9 +16,11 @@ */ package org.apache.kafka.streams.kstream.internals; -import org.apache.kafka.streams.processor.ProcessorContext; +import org.apache.kafka.streams.processor.api.ProcessorContext; +import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.processor.StateStore; import org.apache.kafka.streams.processor.To; +import org.apache.kafka.streams.processor.internals.InternalProcessorContext; import org.apache.kafka.streams.state.internals.WrappedStateStore; /** @@ -30,20 +32,40 @@ * @param the type of the value */ class TimestampedTupleForwarder { - private final ProcessorContext context; + private final InternalProcessorContext> context; private final boolean sendOldValues; private final boolean cachingEnabled; - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) TimestampedTupleForwarder(final StateStore store, - final ProcessorContext context, + final ProcessorContext> context, final TimestampedCacheFlushListener flushListener, final boolean sendOldValues) { - this.context = context; + this.context = (InternalProcessorContext>) context; this.sendOldValues = sendOldValues; cachingEnabled = ((WrappedStateStore) store).setFlushListener(flushListener, sendOldValues); } + @SuppressWarnings({"unchecked", "rawtypes"}) + TimestampedTupleForwarder(final StateStore store, + final org.apache.kafka.streams.processor.ProcessorContext context, + final TimestampedCacheFlushListener flushListener, + final boolean sendOldValues) { + this.context = (InternalProcessorContext) context; + this.sendOldValues = sendOldValues; + cachingEnabled = ((WrappedStateStore) store).setFlushListener(flushListener, sendOldValues); + } + + public void maybeForward(final Record> record) { + if (!cachingEnabled) { + if (sendOldValues) { + context.forward(record); + } else { + context.forward(record.withValue(new Change<>(record.value().newValue, null))); + } + } + } + public void maybeForward(final K key, final V newValue, final V oldValue) { diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/ForeignJoinSubscriptionProcessorSupplier.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/ForeignJoinSubscriptionProcessorSupplier.java index fd95105324472..086c83022add6 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/ForeignJoinSubscriptionProcessorSupplier.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/ForeignJoinSubscriptionProcessorSupplier.java @@ -63,8 +63,8 @@ private final class KTableKTableJoinProcessor extends AbstractProcessor internalProcessorContext = (InternalProcessorContext) context; + droppedRecordsSensor = TaskMetrics.droppedRecordsSensor( Thread.currentThread().getName(), internalProcessorContext.taskId().toString(), internalProcessorContext.metrics() diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/ForeignJoinSubscriptionSendProcessorSupplier.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/ForeignJoinSubscriptionSendProcessorSupplier.java index 97878750dc1f6..4e19b4f64b2aa 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/ForeignJoinSubscriptionSendProcessorSupplier.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/ForeignJoinSubscriptionSendProcessorSupplier.java @@ -88,7 +88,7 @@ public void init(final ProcessorContext context) { if (valueSerializer == null) { valueSerializer = (Serializer) context.valueSerde().serializer(); } - droppedRecordsSensor = TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor( + droppedRecordsSensor = TaskMetrics.droppedRecordsSensor( Thread.currentThread().getName(), context.taskId().toString(), (StreamsMetricsImpl) context.metrics() diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/SubscriptionStoreReceiveProcessorSupplier.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/SubscriptionStoreReceiveProcessorSupplier.java index 61fb1c1334774..4746fc6e9461f 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/SubscriptionStoreReceiveProcessorSupplier.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/foreignkeyjoin/SubscriptionStoreReceiveProcessorSupplier.java @@ -60,9 +60,9 @@ public Processor> get() { @Override public void init(final ProcessorContext context) { super.init(context); - final InternalProcessorContext internalProcessorContext = (InternalProcessorContext) context; + final InternalProcessorContext internalProcessorContext = (InternalProcessorContext) context; - droppedRecordsSensor = TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor( + droppedRecordsSensor = TaskMetrics.droppedRecordsSensor( Thread.currentThread().getName(), internalProcessorContext.taskId().toString(), internalProcessorContext.metrics() diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/BufferConfigInternal.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/BufferConfigInternal.java index 74de6ef1d452f..800a2a52bff0f 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/BufferConfigInternal.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/BufferConfigInternal.java @@ -35,18 +35,19 @@ public Suppressed.StrictBufferConfig withNoBound() { return new StrictBufferConfigImpl( Long.MAX_VALUE, Long.MAX_VALUE, - SHUT_DOWN // doesn't matter, given the bounds + SHUT_DOWN, // doesn't matter, given the bounds + getLogConfig() ); } @Override public Suppressed.StrictBufferConfig shutDownWhenFull() { - return new StrictBufferConfigImpl(maxRecords(), maxBytes(), SHUT_DOWN); + return new StrictBufferConfigImpl(maxRecords(), maxBytes(), SHUT_DOWN, getLogConfig()); } @Override public Suppressed.EagerBufferConfig emitEarlyWhenFull() { - return new EagerBufferConfigImpl(maxRecords(), maxBytes()); + return new EagerBufferConfigImpl(maxRecords(), maxBytes(), getLogConfig()); } public abstract boolean isLoggingEnabled(); diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/EagerBufferConfigImpl.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/EagerBufferConfigImpl.java index c56532d7f4775..7665e66742358 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/EagerBufferConfigImpl.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/EagerBufferConfigImpl.java @@ -28,15 +28,9 @@ public class EagerBufferConfigImpl extends BufferConfigInternal logConfig; - public EagerBufferConfigImpl(final long maxRecords, final long maxBytes) { - this.maxRecords = maxRecords; - this.maxBytes = maxBytes; - this.logConfig = Collections.emptyMap(); - } - - private EagerBufferConfigImpl(final long maxRecords, - final long maxBytes, - final Map logConfig) { + public EagerBufferConfigImpl(final long maxRecords, + final long maxBytes, + final Map logConfig) { this.maxRecords = maxRecords; this.maxBytes = maxBytes; this.logConfig = logConfig; @@ -97,16 +91,20 @@ public boolean equals(final Object o) { } final EagerBufferConfigImpl that = (EagerBufferConfigImpl) o; return maxRecords == that.maxRecords && - maxBytes == that.maxBytes; + maxBytes == that.maxBytes && + Objects.equals(getLogConfig(), that.getLogConfig()); } @Override public int hashCode() { - return Objects.hash(maxRecords, maxBytes); + return Objects.hash(maxRecords, maxBytes, getLogConfig()); } @Override public String toString() { - return "EagerBufferConfigImpl{maxRecords=" + maxRecords + ", maxBytes=" + maxBytes + '}'; + return "EagerBufferConfigImpl{maxRecords=" + maxRecords + + ", maxBytes=" + maxBytes + + ", logConfig=" + getLogConfig() + + "}"; } } diff --git a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/StrictBufferConfigImpl.java b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/StrictBufferConfigImpl.java index 13ffccdfffb85..2ca5ef9b4ee72 100644 --- a/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/StrictBufferConfigImpl.java +++ b/streams/src/main/java/org/apache/kafka/streams/kstream/internals/suppress/StrictBufferConfigImpl.java @@ -41,14 +41,6 @@ public StrictBufferConfigImpl(final long maxRecords, this.logConfig = logConfig; } - public StrictBufferConfigImpl(final long maxRecords, - final long maxBytes, - final BufferFullStrategy bufferFullStrategy) { - this.maxRecords = maxRecords; - this.maxBytes = maxBytes; - this.bufferFullStrategy = bufferFullStrategy; - this.logConfig = Collections.emptyMap(); - } public StrictBufferConfigImpl() { this.maxRecords = Long.MAX_VALUE; @@ -59,12 +51,12 @@ public StrictBufferConfigImpl() { @Override public Suppressed.StrictBufferConfig withMaxRecords(final long recordLimit) { - return new StrictBufferConfigImpl(recordLimit, maxBytes, bufferFullStrategy); + return new StrictBufferConfigImpl(recordLimit, maxBytes, bufferFullStrategy, getLogConfig()); } @Override public Suppressed.StrictBufferConfig withMaxBytes(final long byteLimit) { - return new StrictBufferConfigImpl(maxRecords, byteLimit, bufferFullStrategy); + return new StrictBufferConfigImpl(maxRecords, byteLimit, bufferFullStrategy, getLogConfig()); } @Override @@ -113,18 +105,21 @@ public boolean equals(final Object o) { final StrictBufferConfigImpl that = (StrictBufferConfigImpl) o; return maxRecords == that.maxRecords && maxBytes == that.maxBytes && - bufferFullStrategy == that.bufferFullStrategy; + bufferFullStrategy == that.bufferFullStrategy && + Objects.equals(getLogConfig(), ((StrictBufferConfigImpl) o).getLogConfig()); } @Override public int hashCode() { - return Objects.hash(maxRecords, maxBytes, bufferFullStrategy); + return Objects.hash(maxRecords, maxBytes, bufferFullStrategy, getLogConfig()); } @Override public String toString() { return "StrictBufferConfigImpl{maxKeys=" + maxRecords + ", maxBytes=" + maxBytes + - ", bufferFullStrategy=" + bufferFullStrategy + '}'; + ", bufferFullStrategy=" + bufferFullStrategy + + ", logConfig=" + getLogConfig().toString() + + '}'; } } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/To.java b/streams/src/main/java/org/apache/kafka/streams/processor/To.java index fe19dbfa4439b..69c0c5b4b5f54 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/To.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/To.java @@ -89,4 +89,11 @@ public int hashCode() { throw new UnsupportedOperationException("To is unsafe for use in Hash collections"); } + @Override + public String toString() { + return "To{" + + "childName='" + childName + '\'' + + ", timestamp=" + timestamp + + '}'; + } } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/AbstractProcessorContext.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/AbstractProcessorContext.java index 09a2e31f04dc9..79b2d0db565e4 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/AbstractProcessorContext.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/AbstractProcessorContext.java @@ -34,7 +34,7 @@ import java.util.Objects; import java.util.Optional; -public abstract class AbstractProcessorContext implements InternalProcessorContext { +public abstract class AbstractProcessorContext implements InternalProcessorContext { private final TaskId taskId; private final String applicationId; diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ActiveTaskCreator.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ActiveTaskCreator.java index 322ff56e74d00..0ff79d59dff27 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ActiveTaskCreator.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ActiveTaskCreator.java @@ -49,7 +49,7 @@ import static org.apache.kafka.streams.processor.internals.StreamThread.ProcessingMode.EXACTLY_ONCE_V2; class ActiveTaskCreator { - private final InternalTopologyBuilder builder; + private final TopologyMetadata topologyMetadata; private final StreamsConfig config; private final StreamsMetricsImpl streamsMetrics; private final StateDirectory stateDirectory; @@ -64,7 +64,7 @@ class ActiveTaskCreator { private final Map taskProducers; private final StreamThread.ProcessingMode processingMode; - ActiveTaskCreator(final InternalTopologyBuilder builder, + ActiveTaskCreator(final TopologyMetadata topologyMetadata, final StreamsConfig config, final StreamsMetricsImpl streamsMetrics, final StateDirectory stateDirectory, @@ -75,7 +75,7 @@ class ActiveTaskCreator { final String threadId, final UUID processId, final Logger log) { - this.builder = builder; + this.topologyMetadata = topologyMetadata; this.config = config; this.streamsMetrics = streamsMetrics; this.stateDirectory = stateDirectory; @@ -143,7 +143,7 @@ Collection createTasks(final Consumer consumer, final LogContext logContext = getLogContext(taskId); - final ProcessorTopology topology = builder.buildSubtopology(taskId.subtopology()); + final ProcessorTopology topology = topologyMetadata.buildSubtopology(taskId); final ProcessorStateManager stateManager = new ProcessorStateManager( taskId, @@ -194,7 +194,7 @@ StreamTask createActiveTaskFromStandby(final StandbyTask standbyTask, inputPartitions, consumer, logContext, - builder.buildSubtopology(standbyTask.id.subtopology()), + topologyMetadata.buildSubtopology(standbyTask.id), stateManager, context ); diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalProcessorContextImpl.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalProcessorContextImpl.java index be3cf55389b91..dbdd6a211ba2d 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalProcessorContextImpl.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalProcessorContextImpl.java @@ -34,7 +34,7 @@ import static org.apache.kafka.streams.processor.internals.AbstractReadWriteDecorator.getReadWriteStore; -public class GlobalProcessorContextImpl extends AbstractProcessorContext { +public class GlobalProcessorContextImpl extends AbstractProcessorContext { private final GlobalStateManager stateManager; private final Time time; diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalStateManagerImpl.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalStateManagerImpl.java index ae13753715ccc..5c02398ce7257 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalStateManagerImpl.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalStateManagerImpl.java @@ -62,21 +62,22 @@ public class GlobalStateManagerImpl implements GlobalStateManager { private final static long NO_DEADLINE = -1L; - private final Logger log; private final Time time; - private final Consumer globalConsumer; + private final Logger log; private final File baseDir; - private final Set globalStoreNames = new HashSet<>(); - private final FixedOrderMap> globalStores = new FixedOrderMap<>(); - private final StateRestoreListener stateRestoreListener; - private InternalProcessorContext globalProcessorContext; - private final Duration requestTimeoutPlusTaskTimeout; private final long taskTimeoutMs; - private final Set globalNonPersistentStoresTopics = new HashSet<>(); + private final ProcessorTopology topology; private final OffsetCheckpoint checkpointFile; + private final Duration requestTimeoutPlusTaskTimeout; + private final Consumer globalConsumer; + private final StateRestoreListener stateRestoreListener; private final Map checkpointFileCache; private final Map storeToChangelogTopic; - private final List globalStateStores; + private final Set globalStoreNames = new HashSet<>(); + private final Set globalNonPersistentStoresTopics = new HashSet<>(); + private final FixedOrderMap> globalStores = new FixedOrderMap<>(); + + private InternalProcessorContext globalProcessorContext; public GlobalStateManagerImpl(final LogContext logContext, final Time time, @@ -86,14 +87,15 @@ public GlobalStateManagerImpl(final LogContext logContext, final StateRestoreListener stateRestoreListener, final StreamsConfig config) { this.time = time; - storeToChangelogTopic = topology.storeToChangelogTopic(); - globalStateStores = topology.globalStateStores(); + this.topology = topology; baseDir = stateDirectory.globalStateDir(); + storeToChangelogTopic = topology.storeToChangelogTopic(); checkpointFile = new OffsetCheckpoint(new File(baseDir, CHECKPOINT_FILE_NAME)); checkpointFileCache = new HashMap<>(); // Find non persistent store's topics - for (final StateStore store : globalStateStores) { + for (final StateStore store : topology.globalStateStores()) { + globalStoreNames.add(store.name()); if (!store.persistent()) { globalNonPersistentStoresTopics.add(changelogFor(store.name())); } @@ -110,8 +112,7 @@ public GlobalStateManagerImpl(final LogContext logContext, final int requestTimeoutMs = new ClientUtils.QuietConsumerConfig(consumerProps) .getInt(ConsumerConfig.REQUEST_TIMEOUT_MS_CONFIG); taskTimeoutMs = config.getLong(StreamsConfig.TASK_TIMEOUT_MS_CONFIG); - requestTimeoutPlusTaskTimeout = - Duration.ofMillis(requestTimeoutMs + taskTimeoutMs); + requestTimeoutPlusTaskTimeout = Duration.ofMillis(requestTimeoutMs + taskTimeoutMs); } @Override @@ -128,8 +129,7 @@ public Set initialize() { } final Set changelogTopics = new HashSet<>(); - for (final StateStore stateStore : globalStateStores) { - globalStoreNames.add(stateStore.name()); + for (final StateStore stateStore : topology.globalStateStores()) { final String sourceTopic = storeToChangelogTopic.get(stateStore.name()); changelogTopics.add(sourceTopic); stateStore.init((StateStoreContext) globalProcessorContext, stateStore); @@ -166,21 +166,29 @@ public File baseDir() { @Override public void registerStore(final StateStore store, final StateRestoreCallback stateRestoreCallback) { + log.info("Restoring state for global store {}", store.name()); + + // TODO (KAFKA-12887): we should not trigger user's exception handler for illegal-argument but always + // fail-crash; in this case we would not need to immediately close the state store before throwing if (globalStores.containsKey(store.name())) { + store.close(); throw new IllegalArgumentException(String.format("Global Store %s has already been registered", store.name())); } if (!globalStoreNames.contains(store.name())) { + store.close(); throw new IllegalArgumentException(String.format("Trying to register store %s that is not a known global store", store.name())); } + // register the store first, so that if later an exception is thrown then eventually while we call `close` + // on the state manager this state store would be closed as well + globalStores.put(store.name(), Optional.of(store)); + if (stateRestoreCallback == null) { throw new IllegalArgumentException(String.format("The stateRestoreCallback provided for store %s was null", store.name())); } - log.info("Restoring state for global store {}", store.name()); final List topicPartitions = topicPartitionsForStore(store); - final Map highWatermarks = retryUntilSuccessOrThrowOnTaskTimeout( () -> globalConsumer.endOffsets(topicPartitions), String.format( @@ -197,7 +205,6 @@ public void registerStore(final StateStore store, final StateRestoreCallback sta store.name(), converterForStore(store) ); - globalStores.put(store.name(), Optional.of(store)); } finally { globalConsumer.unsubscribe(); } @@ -374,7 +381,7 @@ public void flush() { @Override public void close() { - if (globalStateStores.isEmpty() && globalStores.isEmpty()) { + if (globalStores.isEmpty()) { return; } final StringBuilder closeFailed = new StringBuilder(); @@ -396,20 +403,6 @@ public void close() { log.info("Skipping to close non-initialized store {}", entry.getKey()); } } - for (final StateStore store : globalStateStores) { - if (store.isOpen()) { - try { - store.close(); - } catch (final RuntimeException e) { - log.error("Failed to close global state store {}", store.name(), e); - closeFailed.append("Failed to close global state store:") - .append(store.name()) - .append(". Reason: ") - .append(e) - .append("\n"); - } - } - } if (closeFailed.length() > 0) { throw new ProcessorStateException("Exceptions caught during close of 1 or more global state globalStores\n" + closeFailed); diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalStateUpdateTask.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalStateUpdateTask.java index 6b1378b44bc83..523228542a8be 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalStateUpdateTask.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/GlobalStateUpdateTask.java @@ -30,7 +30,7 @@ import java.util.Map; import java.util.Set; -import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor; +import static org.apache.kafka.streams.processor.internals.metrics.TaskMetrics.droppedRecordsSensor; /** * Updates the state for all Global State Stores. @@ -69,14 +69,14 @@ public Map initialize() { final Map storeNameToTopic = topology.storeToChangelogTopic(); for (final String storeName : storeNames) { final String sourceTopic = storeNameToTopic.get(storeName); - final SourceNode source = topology.source(sourceTopic); + final SourceNode source = topology.source(sourceTopic); deserializers.put( sourceTopic, new RecordDeserializer( source, deserializationExceptionHandler, logContext, - droppedRecordsSensorOrSkippedRecordsSensor( + droppedRecordsSensor( Thread.currentThread().getName(), processorContext.taskId().toString(), processorContext.metrics() @@ -111,7 +111,7 @@ public void update(final ConsumerRecord record) { processorContext.timestamp(), processorContext.headers() ); - ((SourceNode) sourceNodeAndDeserializer.sourceNode()).process(toProcess); + ((SourceNode) sourceNodeAndDeserializer.sourceNode()).process(toProcess); } offsets.put(new TopicPartition(record.topic(), record.partition()), record.offset() + 1); @@ -138,6 +138,7 @@ public void close(final boolean wipeStateStore) throws IOException { } } + @SuppressWarnings("unchecked") private void initTopology() { for (final ProcessorNode node : this.topology.processors()) { processorContext.setCurrentNode(node); diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/InternalProcessorContext.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/InternalProcessorContext.java index ba5c580d78de9..88e47e3c4580d 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/InternalProcessorContext.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/InternalProcessorContext.java @@ -34,8 +34,8 @@ * {@link ProcessorNode} when we are forwarding items that have been evicted or flushed from * {@link ThreadCache} */ -public interface InternalProcessorContext - extends ProcessorContext, org.apache.kafka.streams.processor.api.ProcessorContext, StateStoreContext { +public interface InternalProcessorContext + extends ProcessorContext, org.apache.kafka.streams.processor.api.ProcessorContext, StateStoreContext { BytesSerializer BYTES_KEY_SERIALIZER = new BytesSerializer(); ByteArraySerializer BYTEARRAY_VALUE_SERIALIZER = new ByteArraySerializer(); diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/InternalTopologyBuilder.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/InternalTopologyBuilder.java index db7c91cdfe05f..bf7a27ced3348 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/InternalTopologyBuilder.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/InternalTopologyBuilder.java @@ -16,6 +16,7 @@ */ package org.apache.kafka.streams.processor.internals; +import org.apache.kafka.clients.consumer.OffsetResetStrategy; import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.serialization.Deserializer; import org.apache.kafka.common.serialization.Serializer; @@ -56,10 +57,13 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import static org.apache.kafka.clients.consumer.OffsetResetStrategy.EARLIEST; +import static org.apache.kafka.clients.consumer.OffsetResetStrategy.LATEST; +import static org.apache.kafka.clients.consumer.OffsetResetStrategy.NONE; + public class InternalTopologyBuilder { private static final Logger log = LoggerFactory.getLogger(InternalTopologyBuilder.class); - private static final Pattern EMPTY_ZERO_LENGTH_PATTERN = Pattern.compile(""); private static final String[] NO_PREDECESSORS = {}; // node factories in a topological order @@ -122,7 +126,7 @@ public class InternalTopologyBuilder { private String applicationId = null; - private Pattern sourceTopicPattern = null; + private String sourceTopicPatternString = null; private List sourceTopicCollection = null; @@ -131,15 +135,9 @@ public class InternalTopologyBuilder { private StreamsConfig config = null; // The name of the topology this builder belongs to, or null if none - private final String namedTopology; - - public InternalTopologyBuilder() { - this.namedTopology = null; - } + private String namedTopology; - public InternalTopologyBuilder(final String namedTopology) { - this.namedTopology = namedTopology; - } + private boolean hasPersistentStores = false; public static class StateStoreFactory { private final StoreBuilder builder; @@ -243,7 +241,7 @@ Processor describe() { // even if it can be matched by multiple regex patterns. Only used by SourceNodeFactory private final Map topicToPatterns = new HashMap<>(); - private class SourceNodeFactory extends NodeFactory { + private class SourceNodeFactory extends NodeFactory { private final List topics; private final Pattern pattern; private final Deserializer keyDeserializer; @@ -291,7 +289,7 @@ List getTopics(final Collection subscribedTopics) { } @Override - public ProcessorNode build() { + public ProcessorNode build() { return new SourceNode<>(name, timestampExtractor, keyDeserializer, valDeserializer); } @@ -305,7 +303,7 @@ Source describe() { } } - private class SinkNodeFactory extends NodeFactory { + private class SinkNodeFactory extends NodeFactory { private final Serializer keySerializer; private final Serializer valSerializer; private final StreamPartitioner partitioner; @@ -325,7 +323,7 @@ private SinkNodeFactory(final String name, } @Override - public ProcessorNode build() { + public ProcessorNode build() { if (topicExtractor instanceof StaticTopicNameExtractor) { final String topic = ((StaticTopicNameExtractor) topicExtractor).topicName; if (internalTopicNamesWithProperties.containsKey(topic)) { @@ -345,8 +343,17 @@ Sink describe() { } } + public void setTopologyName(final String namedTopology) { + Objects.requireNonNull(namedTopology, "named topology can't be null"); + if (this.namedTopology != null) { + log.error("Tried to reset the namedTopology to {} but it was already set to {}", namedTopology, this.namedTopology); + throw new IllegalStateException("NamedTopology has already been set to " + this.namedTopology); + } + this.namedTopology = namedTopology; + } + // public for testing only - public synchronized final InternalTopologyBuilder setApplicationId(final String applicationId) { + public final InternalTopologyBuilder setApplicationId(final String applicationId) { Objects.requireNonNull(applicationId, "applicationId can't be null"); this.applicationId = applicationId; @@ -364,6 +371,10 @@ public synchronized final StreamsConfig getStreamsConfig() { return config; } + public String namedTopology() { + return namedTopology; + } + public synchronized final InternalTopologyBuilder rewriteTopology(final StreamsConfig config) { Objects.requireNonNull(config, "config can't be null"); @@ -630,8 +641,8 @@ public final void connectProcessorAndStateStores(final String processorName, nodeGroups = null; } - public Map getChangelogTopicToStore() { - return changelogTopicToStore; + public String getStoreForChangelogTopic(final String topicName) { + return changelogTopicToStore.get(topicName); } public void connectSourceStoreAndTopic(final String sourceStoreName, @@ -761,12 +772,12 @@ private void connectProcessorAndStateStore(final String processorName, } } - private Set> findSourcesForProcessorPredecessors(final String[] predecessors) { - final Set> sourceNodes = new HashSet<>(); + private Set> findSourcesForProcessorPredecessors(final String[] predecessors) { + final Set> sourceNodes = new HashSet<>(); for (final String predecessor : predecessors) { final NodeFactory nodeFactory = nodeFactories.get(predecessor); if (nodeFactory instanceof SourceNodeFactory) { - sourceNodes.add((SourceNodeFactory) nodeFactory); + sourceNodes.add((SourceNodeFactory) nodeFactory); } else if (nodeFactory instanceof ProcessorNodeFactory) { sourceNodes.addAll(findSourcesForProcessorPredecessors(((ProcessorNodeFactory) nodeFactory).predecessors)); } @@ -787,10 +798,10 @@ private void connectStateStoreNameToSourceTopicsOrPattern final Set sourceTopics = new HashSet<>(); final Set sourcePatterns = new HashSet<>(); - final Set> sourceNodesForPredecessor = + final Set> sourceNodesForPredecessor = findSourcesForProcessorPredecessors(processorNodeFactory.predecessors); - for (final SourceNodeFactory sourceNodeFactory : sourceNodesForPredecessor) { + for (final SourceNodeFactory sourceNodeFactory : sourceNodesForPredecessor) { if (sourceNodeFactory.pattern != null) { sourcePatterns.add(sourceNodeFactory.pattern); } else { @@ -925,8 +936,8 @@ private ProcessorTopology build(final Set nodeGroup) { Objects.requireNonNull(applicationId, "topology has not completed optimization"); final Map> processorMap = new LinkedHashMap<>(); - final Map> topicSourceMap = new HashMap<>(); - final Map> topicSinkMap = new HashMap<>(); + final Map> topicSourceMap = new HashMap<>(); + final Map> topicSinkMap = new HashMap<>(); final Map stateStoreMap = new LinkedHashMap<>(); final Set repartitionTopics = new HashSet<>(); @@ -946,15 +957,15 @@ private ProcessorTopology build(final Set nodeGroup) { } else if (factory instanceof SourceNodeFactory) { buildSourceNode(topicSourceMap, repartitionTopics, - (SourceNodeFactory) factory, - (SourceNode) node); + (SourceNodeFactory) factory, + (SourceNode) node); } else if (factory instanceof SinkNodeFactory) { buildSinkNode(processorMap, topicSinkMap, repartitionTopics, - (SinkNodeFactory) factory, - (SinkNode) node); + (SinkNodeFactory) factory, + (SinkNode) node); } else { throw new TopologyException("Unknown definition class: " + factory.getClass().getName()); } @@ -971,13 +982,16 @@ private ProcessorTopology build(final Set nodeGroup) { } private void buildSinkNode(final Map> processorMap, - final Map> topicSinkMap, + final Map> topicSinkMap, final Set repartitionTopics, - final SinkNodeFactory sinkNodeFactory, - final SinkNode node) { + final SinkNodeFactory sinkNodeFactory, + final SinkNode node) { + @SuppressWarnings("unchecked") final ProcessorNode sinkNode = + (ProcessorNode) node; for (final String predecessorName : sinkNodeFactory.predecessors) { - getProcessor(processorMap, predecessorName).addChild(node); + final ProcessorNode processor = getProcessor(processorMap, predecessorName); + processor.addChild(sinkNode); if (sinkNodeFactory.topicExtractor instanceof StaticTopicNameExtractor) { final String topic = ((StaticTopicNameExtractor) sinkNodeFactory.topicExtractor).topicName; @@ -1002,10 +1016,10 @@ private static ProcessorNode getPro return (ProcessorNode) processorMap.get(predecessor); } - private void buildSourceNode(final Map> topicSourceMap, + private void buildSourceNode(final Map> topicSourceMap, final Set repartitionTopics, - final SourceNodeFactory sourceNodeFactory, - final SourceNode node) { + final SourceNodeFactory sourceNodeFactory, + final SourceNode node) { final List topics = (sourceNodeFactory.pattern != null) ? sourceNodeFactory.getTopics(subscriptionUpdates()) : @@ -1039,13 +1053,23 @@ private void buildProcessorNode(final Map> pro // remember the changelog topic if this state store is change-logging enabled if (stateStoreFactory.loggingEnabled() && !storeToChangelogTopic.containsKey(stateStoreName)) { - final String changelogTopic = ProcessorStateManager.storeChangelogTopic(applicationId, stateStoreName); + final String changelogTopic = + ProcessorStateManager.storeChangelogTopic(applicationId, stateStoreName, namedTopology); storeToChangelogTopic.put(stateStoreName, changelogTopic); changelogTopicToStore.put(changelogTopic, stateStoreName); } - stateStoreMap.put(stateStoreName, stateStoreFactory.build()); + final StateStore store = stateStoreFactory.build(); + stateStoreMap.put(stateStoreName, store); + if (store.persistent()) { + hasPersistentStores = true; + } + } else { - stateStoreMap.put(stateStoreName, globalStateStores.get(stateStoreName)); + final StateStore store = globalStateStores.get(stateStoreName); + stateStoreMap.put(stateStoreName, store); + if (store.persistent()) { + hasPersistentStores = true; + } } } } @@ -1062,7 +1086,7 @@ public Map globalStateStores() { return Collections.unmodifiableMap(globalStateStores); } - public Set allStateStoreName() { + public Set allStateStoreNames() { Objects.requireNonNull(applicationId, "topology has not completed optimization"); final Set allNames = new HashSet<>(stateFactories.keySet()); @@ -1070,6 +1094,14 @@ public Set allStateStoreName() { return Collections.unmodifiableSet(allNames); } + public boolean hasStore(final String name) { + return stateFactories.containsKey(name) || globalStateStores.containsKey(name); + } + + public boolean hasPersistentStores() { + return hasPersistentStores; + } + /** * Returns the map of topic groups keyed by the group id. * A topic group is a group of topics in the same task. @@ -1168,7 +1200,7 @@ private RepartitionTopicConfig buildRepartitionTopicConfig(final String internal private void setRegexMatchedTopicsToSourceNodes() { if (hasSubscriptionUpdates()) { for (final String nodeName : nodeToSourcePatterns.keySet()) { - final SourceNodeFactory sourceNode = (SourceNodeFactory) nodeFactories.get(nodeName); + final SourceNodeFactory sourceNode = (SourceNodeFactory) nodeFactories.get(nodeName); final List sourceTopics = sourceNode.getTopics(subscriptionUpdates); //need to update nodeToSourceTopics and sourceTopicNames with topics matched from given regex nodeToSourceTopics.put(nodeName, sourceTopics); @@ -1213,39 +1245,26 @@ private InternalTopicConfig createChangelogTopicConfig(fi } } - public synchronized Pattern earliestResetTopicsPattern() { - return resetTopicsPattern(earliestResetTopics, earliestResetPatterns); - } - - public synchronized Pattern latestResetTopicsPattern() { - return resetTopicsPattern(latestResetTopics, latestResetPatterns); + public boolean hasOffsetResetOverrides() { + return !(earliestResetTopics.isEmpty() && earliestResetPatterns.isEmpty() + && latestResetTopics.isEmpty() && latestResetPatterns.isEmpty()); } - private Pattern resetTopicsPattern(final Set resetTopics, - final Set resetPatterns) { - final List topics = maybeDecorateInternalSourceTopics(resetTopics); - - return buildPattern(topics, resetPatterns); - } - - private static Pattern buildPattern(final Collection sourceTopics, - final Collection sourcePatterns) { - final StringBuilder builder = new StringBuilder(); - - for (final String topic : sourceTopics) { - builder.append(topic).append("|"); - } - - for (final Pattern sourcePattern : sourcePatterns) { - builder.append(sourcePattern.pattern()).append("|"); - } - - if (builder.length() > 0) { - builder.setLength(builder.length() - 1); - return Pattern.compile(builder.toString()); + public OffsetResetStrategy offsetResetStrategy(final String topic) { + if (maybeDecorateInternalSourceTopics(earliestResetTopics).contains(topic) || + earliestResetPatterns.stream().anyMatch(p -> p.matcher(topic).matches())) { + return EARLIEST; + } else if (maybeDecorateInternalSourceTopics(latestResetTopics).contains(topic) || + latestResetPatterns.stream().anyMatch(p -> p.matcher(topic).matches())) { + return LATEST; + } else if (maybeDecorateInternalSourceTopics(sourceTopicNames).contains(topic) + || (usesPatternSubscription() && Pattern.compile(sourceTopicPatternString).matcher(topic).matches()) + || !hasNamedTopology()) { + return NONE; + } else { + // return null if the topic wasn't found at all while using NamedTopologies as it's likely in another + return null; } - - return EMPTY_ZERO_LENGTH_PATTERN; } public Map> stateStoreNameToSourceTopics() { @@ -1318,19 +1337,20 @@ public String decoratePseudoTopic(final String topic) { private String decorateTopic(final String topic) { if (applicationId == null) { throw new TopologyException("there are internal topics and " - + "applicationId hasn't been set. Call " - + "setApplicationId first"); + + "applicationId hasn't been set. Call " + + "setApplicationId first"); + } + if (hasNamedTopology()) { + return applicationId + "-" + namedTopology + "-" + topic; + } else { + return applicationId + "-" + topic; } - - return applicationId + "-" + topic; } void initializeSubscription() { if (usesPatternSubscription()) { log.debug("Found pattern subscribed source topics, initializing consumer's subscription pattern."); - final List allSourceTopics = maybeDecorateInternalSourceTopics(sourceTopicNames); - Collections.sort(allSourceTopics); - sourceTopicPattern = buildPattern(allSourceTopics, nodeToSourcePatterns.values()); + sourceTopicPatternString = buildSourceTopicsPatternString(); } else { log.debug("No source topics using pattern subscription found, initializing consumer's subscription collection."); sourceTopicCollection = maybeDecorateInternalSourceTopics(sourceTopicNames); @@ -1338,6 +1358,27 @@ void initializeSubscription() { } } + private String buildSourceTopicsPatternString() { + final List allSourceTopics = maybeDecorateInternalSourceTopics(sourceTopicNames); + Collections.sort(allSourceTopics); + + final StringBuilder builder = new StringBuilder(); + + for (final String topic : allSourceTopics) { + builder.append(topic).append("|"); + } + + for (final Pattern sourcePattern : nodeToSourcePatterns.values()) { + builder.append(sourcePattern.pattern()).append("|"); + } + + if (builder.length() > 0) { + builder.setLength(builder.length() - 1); + } + + return builder.toString(); + } + boolean usesPatternSubscription() { return !nodeToSourcePatterns.isEmpty(); } @@ -1346,26 +1387,35 @@ synchronized Collection sourceTopicCollection() { return sourceTopicCollection; } - synchronized Pattern sourceTopicPattern() { - return sourceTopicPattern; + synchronized String sourceTopicsPatternString() { + // With a NamedTopology, it may be that this topology does not use pattern subscription but another one does + // in which case we would need to initialize the pattern string where we would otherwise have not + if (sourceTopicPatternString == null && hasNamedTopology()) { + sourceTopicPatternString = buildSourceTopicsPatternString(); + } + return sourceTopicPatternString; } public boolean hasNoNonGlobalTopology() { - return !usesPatternSubscription() && sourceTopicCollection().isEmpty(); + return nodeToSourcePatterns.isEmpty() && sourceTopicNames.isEmpty(); + } + + public boolean hasGlobalStores() { + return !globalStateStores.isEmpty(); } private boolean isGlobalSource(final String nodeName) { final NodeFactory nodeFactory = nodeFactories.get(nodeName); if (nodeFactory instanceof SourceNodeFactory) { - final List topics = ((SourceNodeFactory) nodeFactory).topics; + final List topics = ((SourceNodeFactory) nodeFactory).topics; return topics != null && topics.size() == 1 && globalTopics.contains(topics.get(0)); } return false; } public TopologyDescription describe() { - final TopologyDescription description = new TopologyDescription(); + final TopologyDescription description = new TopologyDescription(namedTopology); for (final Map.Entry> nodeGroup : makeNodeGroups().entrySet()) { @@ -1901,6 +1951,15 @@ public int compare(final TopologyDescription.Subtopology subtopology1, public final static class TopologyDescription implements org.apache.kafka.streams.TopologyDescription { private final TreeSet subtopologies = new TreeSet<>(SUBTOPOLOGY_COMPARATOR); private final TreeSet globalStores = new TreeSet<>(GLOBALSTORE_COMPARATOR); + private final String namedTopology; + + public TopologyDescription() { + this(null); + } + + public TopologyDescription(final String namedTopology) { + this.namedTopology = namedTopology; + } public void addSubtopology(final TopologyDescription.Subtopology subtopology) { subtopologies.add(subtopology); @@ -1923,7 +1982,12 @@ public Set globalStores() { @Override public String toString() { final StringBuilder sb = new StringBuilder(); - sb.append("Topologies:\n "); + + if (namedTopology == null) { + sb.append("Topologies:\n "); + } else { + sb.append("Topology - ").append(namedTopology).append(":\n "); + } final TopologyDescription.Subtopology[] sortedSubtopologies = subtopologies.descendingSet().toArray(new TopologyDescription.Subtopology[0]); final TopologyDescription.GlobalStore[] sortedGlobalStores = @@ -2035,13 +2099,22 @@ private void updateSubscribedTopics(final Set topics, final String logPr setRegexMatchedTopicToStateStore(); } + /** + * @return a copy of all source topic names, including the application id and named topology prefix if applicable + */ public synchronized List fullSourceTopicNames() { - return maybeDecorateInternalSourceTopics(sourceTopicNames); + return new ArrayList<>(maybeDecorateInternalSourceTopics(sourceTopicNames)); } - public boolean hasNamedTopologies() { - // TODO KAFKA-12648: covered by Pt. 2 - return false; + /** + * @return a copy of the string representation of any pattern subscribed source nodes + */ + public synchronized List allSourcePatternStrings() { + return nodeToSourcePatterns.values().stream().map(Pattern::pattern).collect(Collectors.toList()); + } + + public boolean hasNamedTopology() { + return namedTopology != null; } // following functions are for test only diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorContextImpl.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorContextImpl.java index dcae2abc0570b..bd7ece4411513 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorContextImpl.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorContextImpl.java @@ -42,7 +42,7 @@ import static org.apache.kafka.streams.processor.internals.AbstractReadOnlyDecorator.getReadOnlyStore; import static org.apache.kafka.streams.processor.internals.AbstractReadWriteDecorator.getReadWriteStore; -public class ProcessorContextImpl extends AbstractProcessorContext implements RecordCollector.Supplier { +public class ProcessorContextImpl extends AbstractProcessorContext implements RecordCollector.Supplier { // the below are null for standby tasks private StreamTask streamTask; private RecordCollector collector; diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorContextUtils.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorContextUtils.java index 1c6551124d363..04c1fd3dac64b 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorContextUtils.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorContextUtils.java @@ -59,13 +59,13 @@ public static StreamsMetricsImpl getMetricsImpl(final StateStoreContext context) public static String changelogFor(final ProcessorContext context, final String storeName) { return context instanceof InternalProcessorContext ? ((InternalProcessorContext) context).changelogFor(storeName) - : ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName); + : ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName, context.taskId().namedTopology()); } public static String changelogFor(final StateStoreContext context, final String storeName) { return context instanceof InternalProcessorContext ? ((InternalProcessorContext) context).changelogFor(storeName) - : ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName); + : ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName, context.taskId().namedTopology()); } public static InternalProcessorContext asInternalProcessorContext(final ProcessorContext context) { diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorNode.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorNode.java index a8c32c7882c9d..9bf35d4f16718 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorNode.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorNode.java @@ -16,16 +16,12 @@ */ package org.apache.kafka.streams.processor.internals; -import org.apache.kafka.common.metrics.Sensor; import org.apache.kafka.common.utils.SystemTime; import org.apache.kafka.common.utils.Time; import org.apache.kafka.streams.errors.StreamsException; import org.apache.kafka.streams.processor.Punctuator; import org.apache.kafka.streams.processor.api.Processor; -import org.apache.kafka.streams.processor.api.ProcessorContext; import org.apache.kafka.streams.processor.api.Record; -import org.apache.kafka.streams.processor.internals.metrics.ProcessorNodeMetrics; -import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl; import java.util.ArrayList; import java.util.HashMap; @@ -33,8 +29,6 @@ import java.util.Map; import java.util.Set; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.maybeMeasureLatency; - public class ProcessorNode { private final List> children; @@ -49,11 +43,6 @@ public class ProcessorNode { private InternalProcessorContext internalProcessorContext; private String threadId; - private Sensor processSensor; - private Sensor punctuateSensor; - private Sensor destroySensor; - private Sensor createSensor; - private boolean closed = true; public ProcessorNode(final String name) { @@ -105,23 +94,16 @@ public void addChild(final ProcessorNode child) { childByName.put(child.name, child); } - @SuppressWarnings("unchecked") - public void init(final InternalProcessorContext context) { + public void init(final InternalProcessorContext context) { if (!closed) throw new IllegalStateException("The processor is not closed"); try { + threadId = Thread.currentThread().getName(); internalProcessorContext = context; - initSensors(); - maybeMeasureLatency( - () -> { - if (processor != null) { - processor.init((ProcessorContext) context); - } - }, - time, - createSensor - ); + if (processor != null) { + processor.init(context); + } } catch (final Exception e) { throw new StreamsException(String.format("failed to initialize processor %s", name), e); } @@ -131,29 +113,13 @@ public void init(final InternalProcessorContext context) { closed = false; } - private void initSensors() { - threadId = Thread.currentThread().getName(); - final String taskId = internalProcessorContext.taskId().toString(); - final StreamsMetricsImpl streamsMetrics = internalProcessorContext.metrics(); - processSensor = ProcessorNodeMetrics.processSensor(threadId, taskId, name, streamsMetrics); - punctuateSensor = ProcessorNodeMetrics.punctuateSensor(threadId, taskId, name, streamsMetrics); - createSensor = ProcessorNodeMetrics.createSensor(threadId, taskId, name, streamsMetrics); - destroySensor = ProcessorNodeMetrics.destroySensor(threadId, taskId, name, streamsMetrics); - } - public void close() { throwIfClosed(); try { - maybeMeasureLatency( - () -> { - if (processor != null) { - processor.close(); - } - }, - time, - destroySensor - ); + if (processor != null) { + processor.close(); + } internalProcessorContext.metrics().removeAllNodeLevelSensors( threadId, internalProcessorContext.taskId().toString(), @@ -177,7 +143,7 @@ public void process(final Record record) { throwIfClosed(); try { - maybeMeasureLatency(() -> processor.process(record), time, processSensor); + processor.process(record); } catch (final ClassCastException e) { final String keyClass = record.key() == null ? "unknown because key is null" : record.key().getClass().getName(); final String valueClass = record.value() == null ? "unknown because value is null" : record.value().getClass().getName(); @@ -196,7 +162,7 @@ public void process(final Record record) { } public void punctuate(final long timestamp, final Punctuator punctuator) { - maybeMeasureLatency(() -> punctuator.punctuate(timestamp), time, punctuateSensor); + punctuator.punctuate(timestamp); } public boolean isTerminalNode() { diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorStateManager.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorStateManager.java index 9d646676335b7..b507aea549a9d 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorStateManager.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorStateManager.java @@ -162,8 +162,12 @@ public String toString() { private TaskType taskType; - public static String storeChangelogTopic(final String applicationId, final String storeName) { - return applicationId + "-" + storeName + STATE_CHANGELOG_TOPIC_SUFFIX; + public static String storeChangelogTopic(final String applicationId, final String storeName, final String namedTopology) { + if (namedTopology == null) { + return applicationId + "-" + storeName + STATE_CHANGELOG_TOPIC_SUFFIX; + } else { + return applicationId + "-" + namedTopology + "-" + storeName + STATE_CHANGELOG_TOPIC_SUFFIX; + } } /** @@ -306,12 +310,16 @@ public File baseDir() { public void registerStore(final StateStore store, final StateRestoreCallback stateRestoreCallback) { final String storeName = store.name(); + // TODO (KAFKA-12887): we should not trigger user's exception handler for illegal-argument but always + // fail-crash; in this case we would not need to immediately close the state store before throwing if (CHECKPOINT_FILE_NAME.equals(storeName)) { + store.close(); throw new IllegalArgumentException(format("%sIllegal store name: %s, which collides with the pre-defined " + "checkpoint file name", logPrefix, storeName)); } if (stores.containsKey(storeName)) { + store.close(); throw new IllegalArgumentException(format("%sStore %s has already been registered.", logPrefix, storeName)); } @@ -328,7 +336,8 @@ public void registerStore(final StateStore store, final StateRestoreCallback sta converterForStore(store)) : new StateStoreMetadata(store); - + // register the store first, so that if later an exception is thrown then eventually while we call `close` + // on the state manager this state store would be closed as well stores.put(storeName, storeMetadata); maybeRegisterStoreWithChangelogReader(storeName); diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorTopology.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorTopology.java index 0a0118ae0087a..d2383c7adbb1c 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorTopology.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/ProcessorTopology.java @@ -27,18 +27,16 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; public class ProcessorTopology { private final Logger log = LoggerFactory.getLogger(ProcessorTopology.class); private final List> processorNodes; - private final Map> sourceNodesByName; - private final Map> sourceNodesByTopic; - private final Map> sinksByTopic; + private final Map> sourceNodesByName; + private final Map> sourceNodesByTopic; + private final Map> sinksByTopic; private final Set terminalNodes; private final List stateStores; - private final Set stateStoreNames; private final Set repartitionTopics; // the following contains entries for the entire topology, eg stores that do not belong to this ProcessorTopology @@ -46,8 +44,8 @@ public class ProcessorTopology { private final Map storeToChangelogTopic; public ProcessorTopology(final List> processorNodes, - final Map> sourceNodesByTopic, - final Map> sinksByTopic, + final Map> sourceNodesByTopic, + final Map> sinksByTopic, final List stateStores, final List globalStateStores, final Map storeToChangelogTopic, @@ -56,7 +54,6 @@ public ProcessorTopology(final List> processorNodes, this.sourceNodesByTopic = new HashMap<>(sourceNodesByTopic); this.sinksByTopic = Collections.unmodifiableMap(sinksByTopic); this.stateStores = Collections.unmodifiableList(stateStores); - stateStoreNames = stateStores.stream().map(StateStore::name).collect(Collectors.toSet()); this.globalStateStores = Collections.unmodifiableList(globalStateStores); this.storeToChangelogTopic = Collections.unmodifiableMap(storeToChangelogTopic); this.repartitionTopics = Collections.unmodifiableSet(repartitionTopics); @@ -69,7 +66,7 @@ public ProcessorTopology(final List> processorNodes, } this.sourceNodesByName = new HashMap<>(); - for (final SourceNode source : sourceNodesByTopic.values()) { + for (final SourceNode source : sourceNodesByTopic.values()) { sourceNodesByName.put(source.name(), source); } } @@ -78,11 +75,11 @@ public Set sourceTopics() { return sourceNodesByTopic.keySet(); } - public SourceNode source(final String topic) { + public SourceNode source(final String topic) { return sourceNodesByTopic.get(topic); } - public Set> sources() { + public Set> sources() { return new HashSet<>(sourceNodesByTopic.values()); } @@ -90,7 +87,7 @@ public Set sinkTopics() { return sinksByTopic.keySet(); } - public SinkNode sink(final String topic) { + public SinkNode sink(final String topic) { return sinksByTopic.get(topic); } @@ -106,10 +103,6 @@ public List stateStores() { return stateStores; } - public boolean hasStore(final String storeName) { - return stateStoreNames.contains(storeName); - } - public List globalStateStores() { return Collections.unmodifiableList(globalStateStores); } @@ -151,9 +144,9 @@ public boolean hasPersistentGlobalStore() { public void updateSourceTopics(final Map> allSourceTopicsByNodeName) { sourceNodesByTopic.clear(); - for (final Map.Entry> sourceNodeEntry : sourceNodesByName.entrySet()) { + for (final Map.Entry> sourceNodeEntry : sourceNodesByName.entrySet()) { final String sourceNodeName = sourceNodeEntry.getKey(); - final SourceNode sourceNode = sourceNodeEntry.getValue(); + final SourceNode sourceNode = sourceNodeEntry.getValue(); final List updatedSourceTopics = allSourceTopicsByNodeName.get(sourceNodeName); if (updatedSourceTopics == null) { @@ -211,10 +204,10 @@ public String toString() { * @return A string representation of this instance. */ public String toString(final String indent) { - final Map, List> sourceToTopics = new HashMap<>(); - for (final Map.Entry> sourceNodeEntry : sourceNodesByTopic.entrySet()) { + final Map, List> sourceToTopics = new HashMap<>(); + for (final Map.Entry> sourceNodeEntry : sourceNodesByTopic.entrySet()) { final String topic = sourceNodeEntry.getKey(); - final SourceNode source = sourceNodeEntry.getValue(); + final SourceNode source = sourceNodeEntry.getValue(); sourceToTopics.computeIfAbsent(source, s -> new ArrayList<>()); sourceToTopics.get(source).add(topic); } @@ -222,8 +215,8 @@ public String toString(final String indent) { final StringBuilder sb = new StringBuilder(indent + "ProcessorTopology:\n"); // start from sources - for (final Map.Entry, List> sourceNodeEntry : sourceToTopics.entrySet()) { - final SourceNode source = sourceNodeEntry.getKey(); + for (final Map.Entry, List> sourceNodeEntry : sourceToTopics.entrySet()) { + final SourceNode source = sourceNodeEntry.getKey(); final List topics = sourceNodeEntry.getValue(); sb.append(source.toString(indent + "\t")) .append(topicsToString(indent + "\t", topics)) diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordCollectorImpl.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordCollectorImpl.java index 16a451d0b1550..f8c9cf9d7d870 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordCollectorImpl.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordCollectorImpl.java @@ -82,7 +82,7 @@ public RecordCollectorImpl(final LogContext logContext, this.eosEnabled = streamsProducer.eosEnabled(); final String threadId = Thread.currentThread().getName(); - this.droppedRecordsSensor = TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor(threadId, taskId.toString(), streamsMetrics); + this.droppedRecordsSensor = TaskMetrics.droppedRecordsSensor(threadId, taskId.toString(), streamsMetrics); this.offsets = new HashMap<>(); } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordDeserializer.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordDeserializer.java index 20f144922ad2d..a965187228a37 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordDeserializer.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordDeserializer.java @@ -31,11 +31,11 @@ class RecordDeserializer { private final Logger log; - private final SourceNode sourceNode; + private final SourceNode sourceNode; private final Sensor droppedRecordsSensor; private final DeserializationExceptionHandler deserializationExceptionHandler; - RecordDeserializer(final SourceNode sourceNode, + RecordDeserializer(final SourceNode sourceNode, final DeserializationExceptionHandler deserializationExceptionHandler, final LogContext logContext, final Sensor droppedRecordsSensor) { @@ -100,7 +100,7 @@ ConsumerRecord deserialize(final ProcessorContext processorConte } } - SourceNode sourceNode() { + SourceNode sourceNode() { return sourceNode; } } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordQueue.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordQueue.java index df7e834f092b1..418a7b0ec5c91 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordQueue.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/RecordQueue.java @@ -39,7 +39,7 @@ public class RecordQueue { public static final long UNKNOWN = ConsumerRecord.NO_TIMESTAMP; private final Logger log; - private final SourceNode source; + private final SourceNode source; private final TopicPartition partition; private final ProcessorContext processorContext; private final TimestampExtractor timestampExtractor; @@ -52,7 +52,7 @@ public class RecordQueue { private final Sensor droppedRecordsSensor; RecordQueue(final TopicPartition partition, - final SourceNode source, + final SourceNode source, final TimestampExtractor timestampExtractor, final DeserializationExceptionHandler deserializationExceptionHandler, final InternalProcessorContext processorContext, @@ -62,7 +62,7 @@ public class RecordQueue { this.fifoQueue = new ArrayDeque<>(); this.timestampExtractor = timestampExtractor; this.processorContext = processorContext; - droppedRecordsSensor = TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor( + droppedRecordsSensor = TaskMetrics.droppedRecordsSensor( Thread.currentThread().getName(), processorContext.taskId().toString(), processorContext.metrics() @@ -85,7 +85,7 @@ void setPartitionTime(final long partitionTime) { * * @return SourceNode */ - public SourceNode source() { + public SourceNode source() { return source; } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/RepartitionTopics.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/RepartitionTopics.java index 8656198c966d4..24c6e81cee9bc 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/RepartitionTopics.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/RepartitionTopics.java @@ -40,18 +40,18 @@ public class RepartitionTopics { private final InternalTopicManager internalTopicManager; - private final InternalTopologyBuilder internalTopologyBuilder; + private final TopologyMetadata topologyMetadata; private final Cluster clusterMetadata; private final CopartitionedTopicsEnforcer copartitionedTopicsEnforcer; private final Logger log; private final Map topicPartitionInfos = new HashMap<>(); - public RepartitionTopics(final InternalTopologyBuilder internalTopologyBuilder, + public RepartitionTopics(final TopologyMetadata topologyMetadata, final InternalTopicManager internalTopicManager, final CopartitionedTopicsEnforcer copartitionedTopicsEnforcer, final Cluster clusterMetadata, final String logPrefix) { - this.internalTopologyBuilder = internalTopologyBuilder; + this.topologyMetadata = topologyMetadata; this.internalTopicManager = internalTopicManager; this.clusterMetadata = clusterMetadata; this.copartitionedTopicsEnforcer = copartitionedTopicsEnforcer; @@ -60,13 +60,13 @@ public RepartitionTopics(final InternalTopologyBuilder internalTopologyBuilder, } public void setup() { - final Map topicGroups = internalTopologyBuilder.topicGroups(); + final Map topicGroups = topologyMetadata.topicGroups(); final Map repartitionTopicMetadata = computeRepartitionTopicConfig(topicGroups, clusterMetadata); // ensure the co-partitioning topics within the group have the same number of partitions, // and enforce the number of partitions for those repartition topics to be the same if they // are co-partitioned as well. - ensureCopartitioning(internalTopologyBuilder.copartitionGroups(), repartitionTopicMetadata, clusterMetadata); + ensureCopartitioning(topologyMetadata.copartitionGroups(), repartitionTopicMetadata, clusterMetadata); // make sure the repartition source topics exist with the right number of partitions, // create these topics if necessary diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/SinkNode.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/SinkNode.java index 2efa537ea4ffc..9091f3eb07721 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/SinkNode.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/SinkNode.java @@ -24,14 +24,14 @@ import static org.apache.kafka.streams.kstream.internals.WrappingNullableUtils.prepareKeySerializer; import static org.apache.kafka.streams.kstream.internals.WrappingNullableUtils.prepareValueSerializer; -public class SinkNode extends ProcessorNode { +public class SinkNode extends ProcessorNode { private Serializer keySerializer; private Serializer valSerializer; private final TopicNameExtractor topicExtractor; private final StreamPartitioner partitioner; - private InternalProcessorContext context; + private InternalProcessorContext context; SinkNode(final String name, final TopicNameExtractor topicExtractor, @@ -50,12 +50,12 @@ public class SinkNode extends ProcessorNode child) { + public void addChild(final ProcessorNode child) { throw new UnsupportedOperationException("sink node does not allow addChild"); } @Override - public void init(final InternalProcessorContext context) { + public void init(final InternalProcessorContext context) { super.init(context); this.context = context; final Serializer contextKeySerializer = ProcessorContextUtils.getKeySerializer(context); diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/SourceNode.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/SourceNode.java index 7198f2f17e93e..e4a98d54eac0f 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/SourceNode.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/SourceNode.java @@ -26,9 +26,9 @@ import static org.apache.kafka.streams.kstream.internals.WrappingNullableUtils.prepareKeyDeserializer; import static org.apache.kafka.streams.kstream.internals.WrappingNullableUtils.prepareValueDeserializer; -public class SourceNode extends ProcessorNode { +public class SourceNode extends ProcessorNode { - private InternalProcessorContext context; + private InternalProcessorContext context; private Deserializer keyDeserializer; private Deserializer valDeserializer; private final TimestampExtractor timestampExtractor; @@ -59,13 +59,13 @@ VIn deserializeValue(final String topic, final Headers headers, final byte[] dat } @Override - public void init(final InternalProcessorContext context) { + public void init(final InternalProcessorContext context) { // It is important to first create the sensor before calling init on the // parent object. Otherwise due to backwards compatibility an empty sensor // without parent is created with the same name. // Once the backwards compatibility is not needed anymore it might be possible to // change this. - processAtSourceSensor = ProcessorNodeMetrics.processorAtSourceSensorOrForwardSensor( + processAtSourceSensor = ProcessorNodeMetrics.processAtSourceSensor( Thread.currentThread().getName(), context.taskId().toString(), context.currentNode().name(), diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StandbyTaskCreator.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StandbyTaskCreator.java index 3f0dd22f1bda2..dc1a7a65fb30f 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StandbyTaskCreator.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StandbyTaskCreator.java @@ -34,7 +34,7 @@ import java.util.Set; class StandbyTaskCreator { - private final InternalTopologyBuilder builder; + private final TopologyMetadata topologyMetadata; private final StreamsConfig config; private final StreamsMetricsImpl streamsMetrics; private final StateDirectory stateDirectory; @@ -43,14 +43,14 @@ class StandbyTaskCreator { private final Logger log; private final Sensor createTaskSensor; - StandbyTaskCreator(final InternalTopologyBuilder builder, + StandbyTaskCreator(final TopologyMetadata topologyMetadata, final StreamsConfig config, final StreamsMetricsImpl streamsMetrics, final StateDirectory stateDirectory, final ChangelogReader storeChangelogReader, final String threadId, final Logger log) { - this.builder = builder; + this.topologyMetadata = topologyMetadata; this.config = config; this.streamsMetrics = streamsMetrics; this.stateDirectory = stateDirectory; @@ -73,8 +73,7 @@ Collection createTasks(final Map> tasksToBeCre for (final Map.Entry> newTaskAndPartitions : tasksToBeCreated.entrySet()) { final TaskId taskId = newTaskAndPartitions.getKey(); final Set partitions = newTaskAndPartitions.getValue(); - - final ProcessorTopology topology = builder.buildSubtopology(taskId.subtopology()); + final ProcessorTopology topology = topologyMetadata.buildSubtopology(taskId); if (topology.hasStateWithChangelogs()) { final ProcessorStateManager stateManager = new ProcessorStateManager( @@ -120,7 +119,7 @@ StandbyTask createStandbyTaskFromActive(final StreamTask streamTask, return createStandbyTask( streamTask.id(), inputPartitions, - builder.buildSubtopology(streamTask.id.subtopology()), + topologyMetadata.buildSubtopology(streamTask.id), stateManager, context ); @@ -148,14 +147,6 @@ StandbyTask createStandbyTask(final TaskId taskId, return task; } - public InternalTopologyBuilder builder() { - return builder; - } - - public StateDirectory stateDirectory() { - return stateDirectory; - } - private LogContext getLogContext(final TaskId taskId) { final String threadIdPrefix = String.format("stream-thread [%s] ", Thread.currentThread().getName()); final String logPrefix = threadIdPrefix + String.format("%s [%s] ", "standby-task", taskId); diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StateDirectory.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StateDirectory.java index 5977503409500..4c1fa921624b9 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StateDirectory.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StateDirectory.java @@ -23,34 +23,34 @@ import org.apache.kafka.streams.errors.StreamsException; import org.apache.kafka.streams.processor.TaskId; -import java.io.FileFilter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.File; +import java.io.FileFilter; import java.io.IOException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; -import java.nio.file.Path; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.apache.kafka.streams.processor.internals.StateManagerUtil.CHECKPOINT_FILE_NAME; import static org.apache.kafka.streams.processor.internals.StateManagerUtil.parseTaskDirectoryName; @@ -143,12 +143,6 @@ public StateDirectory(final StreamsConfig config, final Time time, final boolean } } - public StateDirectory(final StreamsConfig config, final Time time, final boolean hasPersistentStores) { - // TODO KAFKA-12648: Explicitly set hasNamedTopology in each test and remove this constructor - // Will be done in Pt. 2 as we want some of these integration tests to use named topologies - this(config, time, hasPersistentStores, false); - } - private void configurePermissions(final File file) { final Path path = file.toPath(); if (path.getFileSystem().supportedFileAttributeViews().contains("posix")) { diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamTask.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamTask.java index 129407af5436b..b8c7aa5ad70c2 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamTask.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamTask.java @@ -42,7 +42,6 @@ import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.processor.internals.metrics.ProcessorNodeMetrics; import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl; -import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.Version; import org.apache.kafka.streams.processor.internals.metrics.TaskMetrics; import org.apache.kafka.streams.processor.internals.metrics.ThreadMetrics; import org.apache.kafka.streams.state.internals.ThreadCache; @@ -102,7 +101,10 @@ public class StreamTask extends AbstractTask implements ProcessorNodePunctuator, private final Sensor punctuateLatencySensor; private final Sensor bufferedRecordsSensor; private final Map e2eLatencySensors = new HashMap<>(); + + @SuppressWarnings("rawtypes") private final InternalProcessorContext processorContext; + private final RecordQueueCreator recordQueueCreator; private StampedRecord record; @@ -110,6 +112,7 @@ public class StreamTask extends AbstractTask implements ProcessorNodePunctuator, private boolean commitRequested = false; private boolean hasPendingTxCommit = false; + @SuppressWarnings("rawtypes") public StreamTask(final TaskId id, final Set inputPartitions, final ProcessorTopology topology, @@ -179,12 +182,7 @@ public StreamTask(final TaskId id, recordInfo = new PartitionGroup.RecordInfo(); final Sensor enforcedProcessingSensor; - if (streamsMetrics.version() == Version.FROM_0100_TO_24) { - final Sensor parent = ThreadMetrics.commitOverTasksSensor(threadId, streamsMetrics); - enforcedProcessingSensor = TaskMetrics.enforcedProcessingSensor(threadId, taskId, streamsMetrics, parent); - } else { - enforcedProcessingSensor = TaskMetrics.enforcedProcessingSensor(threadId, taskId, streamsMetrics); - } + enforcedProcessingSensor = TaskMetrics.enforcedProcessingSensor(threadId, taskId, streamsMetrics); final long maxTaskIdleMs = config.getLong(StreamsConfig.MAX_TASK_IDLE_MS_CONFIG); partitionGroup = new PartitionGroup( logContext, @@ -317,6 +315,7 @@ public void suspend() { } } + @SuppressWarnings("unchecked") private void closeTopology() { log.trace("Closing processor topology"); @@ -805,6 +804,7 @@ private String getStacktraceString(final RuntimeException e) { * @throws IllegalStateException if the current node is not null * @throws TaskMigratedException if the task producer got fenced (EOS only) */ + @SuppressWarnings("unchecked") @Override public void punctuate(final ProcessorNode node, final long timestamp, @@ -840,6 +840,7 @@ public void punctuate(final ProcessorNode node, } } + @SuppressWarnings("unchecked") private void updateProcessorContext(final ProcessorNode currNode, final long wallClockTime, final ProcessorRecordContext recordContext) { @@ -933,6 +934,7 @@ public Map purgeableOffsets() { return purgeableConsumedOffsets; } + @SuppressWarnings("unchecked") private void initializeTopology() { // initialize the task by initializing all its processor nodes in the topology log.trace("Initializing processor nodes of the topology"); @@ -1107,6 +1109,7 @@ long decodeTimestamp(final String encryptedString) { } } + @SuppressWarnings("rawtypes") public InternalProcessorContext processorContext() { return processorContext; } @@ -1230,7 +1233,7 @@ private RecordQueueCreator(final LogContext logContext, } public RecordQueue createQueue(final TopicPartition partition) { - final SourceNode source = topology.source(partition.topic()); + final SourceNode source = topology.source(partition.topic()); if (source == null) { throw new TopologyException( "Topic is unknown to the topology. " + diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java index da79c77c4f7b0..14673ca9db671 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamThread.java @@ -45,7 +45,6 @@ import org.apache.kafka.streams.processor.internals.assignment.AssignorError; import org.apache.kafka.streams.processor.internals.assignment.ReferenceContainer; import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl; -import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.Version; import org.apache.kafka.streams.processor.internals.metrics.ThreadMetrics; import org.apache.kafka.streams.state.internals.ThreadCache; import org.slf4j.Logger; @@ -297,7 +296,7 @@ public boolean isRunning() { private final Consumer mainConsumer; private final Consumer restoreConsumer; private final Admin adminClient; - private final InternalTopologyBuilder builder; + private final TopologyMetadata topologyMetadata; private final java.util.function.Consumer cacheResizer; private java.util.function.Consumer streamsUncaughtExceptionHandler; @@ -306,7 +305,7 @@ public boolean isRunning() { private AtomicLong cacheResizeSize; private AtomicBoolean leaveGroupRequested; - public static StreamThread create(final InternalTopologyBuilder builder, + public static StreamThread create(final TopologyMetadata topologyMetadata, final StreamsConfig config, final KafkaClientSupplier clientSupplier, final Admin adminClient, @@ -348,7 +347,7 @@ public static StreamThread create(final InternalTopologyBuilder builder, final ThreadCache cache = new ThreadCache(logContext, cacheSizeBytes, streamsMetrics); final ActiveTaskCreator activeTaskCreator = new ActiveTaskCreator( - builder, + topologyMetadata, config, streamsMetrics, stateDirectory, @@ -361,7 +360,7 @@ public static StreamThread create(final InternalTopologyBuilder builder, log ); final StandbyTaskCreator standbyTaskCreator = new StandbyTaskCreator( - builder, + topologyMetadata, config, streamsMetrics, stateDirectory, @@ -377,7 +376,7 @@ public static StreamThread create(final InternalTopologyBuilder builder, streamsMetrics, activeTaskCreator, standbyTaskCreator, - builder, + topologyMetadata, adminClient, stateDirectory, processingMode(config) @@ -391,7 +390,7 @@ public static StreamThread create(final InternalTopologyBuilder builder, final String originalReset = (String) consumerConfigs.get(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG); // If there are any overrides, we never fall through to the consumer, but only handle offset management ourselves. - if (!builder.latestResetTopicsPattern().pattern().isEmpty() || !builder.earliestResetTopicsPattern().pattern().isEmpty()) { + if (topologyMetadata.hasOffsetResetOverrides()) { consumerConfigs.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "none"); } @@ -410,7 +409,7 @@ public static StreamThread create(final InternalTopologyBuilder builder, originalReset, taskManager, streamsMetrics, - builder, + topologyMetadata, threadId, logContext, referenceContainer.assignmentErrorCode, @@ -470,7 +469,7 @@ public StreamThread(final Time time, final String originalReset, final TaskManager taskManager, final StreamsMetricsImpl streamsMetrics, - final InternalTopologyBuilder builder, + final TopologyMetadata topologyMetadata, final String threadId, final LogContext logContext, final AtomicInteger assignmentErrorCode, @@ -504,17 +503,13 @@ public StreamThread(final Time time, // The following sensors are created here but their references are not stored in this object, since within // this object they are not recorded. The sensors are created here so that the stream threads starts with all // its metrics initialised. Otherwise, those sensors would have been created during processing, which could - // lead to missing metrics. For instance, if no task were created, the metrics for created and closed + // lead to missing metrics. If no task were created, the metrics for created and closed // tasks would never be added to the metrics. ThreadMetrics.createTaskSensor(threadId, streamsMetrics); ThreadMetrics.closeTaskSensor(threadId, streamsMetrics); - if (streamsMetrics.version() == Version.FROM_0100_TO_24) { - ThreadMetrics.skipRecordSensor(threadId, streamsMetrics); - ThreadMetrics.commitOverTasksSensor(threadId, streamsMetrics); - } this.time = time; - this.builder = builder; + this.topologyMetadata = topologyMetadata; this.logPrefix = logContext.logPrefix(); this.log = logContext.logger(StreamThread.class); this.rebalanceListener = new StreamsRebalanceListener(time, taskManager, this, this.log, this.assignmentErrorCode); @@ -688,10 +683,10 @@ private void handleTaskMigrated(final TaskMigratedException e) { } private void subscribeConsumer() { - if (builder.usesPatternSubscription()) { - mainConsumer.subscribe(builder.sourceTopicPattern(), rebalanceListener); + if (topologyMetadata.usesPatternSubscription()) { + mainConsumer.subscribe(topologyMetadata.sourceTopicPattern(), rebalanceListener); } else { - mainConsumer.subscribe(builder.sourceTopicCollection(), rebalanceListener); + mainConsumer.subscribe(topologyMetadata.sourceTopicCollection(), rebalanceListener); } } @@ -949,18 +944,24 @@ private void resetOffsets(final Set partitions, final Exception final Set notReset = new HashSet<>(); for (final TopicPartition partition : partitions) { - if (builder.earliestResetTopicsPattern().matcher(partition.topic()).matches()) { - addToResetList(partition, seekToBeginning, "Setting topic '{}' to consume from {} offset", "earliest", loggedTopics); - } else if (builder.latestResetTopicsPattern().matcher(partition.topic()).matches()) { - addToResetList(partition, seekToEnd, "Setting topic '{}' to consume from {} offset", "latest", loggedTopics); - } else { - if ("earliest".equals(originalReset)) { - addToResetList(partition, seekToBeginning, "No custom setting defined for topic '{}' using original config '{}' for offset reset", "earliest", loggedTopics); - } else if ("latest".equals(originalReset)) { - addToResetList(partition, seekToEnd, "No custom setting defined for topic '{}' using original config '{}' for offset reset", "latest", loggedTopics); - } else { - notReset.add(partition); - } + switch (topologyMetadata.offsetResetStrategy(partition.topic())) { + case EARLIEST: + addToResetList(partition, seekToBeginning, "Setting topic '{}' to consume from {} offset", "earliest", loggedTopics); + break; + case LATEST: + addToResetList(partition, seekToEnd, "Setting topic '{}' to consume from {} offset", "latest", loggedTopics); + break; + case NONE: + if ("earliest".equals(originalReset)) { + addToResetList(partition, seekToBeginning, "No custom setting defined for topic '{}' using original config '{}' for offset reset", "earliest", loggedTopics); + } else if ("latest".equals(originalReset)) { + addToResetList(partition, seekToEnd, "No custom setting defined for topic '{}' using original config '{}' for offset reset", "latest", loggedTopics); + } else { + notReset.add(partition); + } + break; + default: + throw new IllegalStateException("Unable to locate topic " + partition.topic() + " in the topology"); } } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamsMetadataState.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamsMetadataState.java index 802be92c77384..dc1238102889a 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamsMetadataState.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamsMetadataState.java @@ -46,16 +46,16 @@ */ public class StreamsMetadataState { public static final HostInfo UNKNOWN_HOST = HostInfo.unavailable(); - private final InternalTopologyBuilder builder; + private final TopologyMetadata topologyMetadata; private final Set globalStores; private final HostInfo thisHost; private List allMetadata = Collections.emptyList(); private Cluster clusterMetadata; private final AtomicReference localMetadata = new AtomicReference<>(null); - public StreamsMetadataState(final InternalTopologyBuilder builder, final HostInfo thisHost) { - this.builder = builder; - this.globalStores = builder.globalStateStores().keySet(); + public StreamsMetadataState(final TopologyMetadata topologyMetadata, final HostInfo thisHost) { + this.topologyMetadata = topologyMetadata; + this.globalStores = this.topologyMetadata.globalStateStores().keySet(); this.thisHost = thisHost; } @@ -92,7 +92,7 @@ public StreamsMetadata getLocalMetadata() { public Collection getAllMetadata() { return Collections.unmodifiableList(allMetadata); } - + /** * Find all of the {@link StreamsMetadata}s for a given storeName * @@ -110,8 +110,8 @@ public synchronized Collection getAllMetadataForStore(final Str return allMetadata; } - final Collection sourceTopics = builder.sourceTopicsForStore(storeName); - if (sourceTopics == null) { + final Collection sourceTopics = topologyMetadata.sourceTopicsForStore(storeName); + if (sourceTopics.isEmpty()) { return Collections.emptyList(); } @@ -239,7 +239,7 @@ private void rebuildMetadata(final Map> activePart } final List rebuiltMetadata = new ArrayList<>(); - final Map> storeToSourceTopics = builder.stateStoreNameToSourceTopics(); + final Map> storeToSourceTopics = topologyMetadata.stateStoreNameToSourceTopics(); Stream.concat(activePartitionHostMap.keySet().stream(), standbyPartitionHostMap.keySet().stream()) .distinct() .forEach(hostInfo -> { @@ -306,7 +306,7 @@ private KeyQueryMetadata getKeyQueryMetadataForKey(final String storeName, } private SourceTopicsInfo getSourceTopicsInfo(final String storeName) { - final List sourceTopics = new ArrayList<>(builder.sourceTopicsForStore(storeName)); + final List sourceTopics = new ArrayList<>(topologyMetadata.sourceTopicsForStore(storeName)); if (sourceTopics.isEmpty()) { return null; } @@ -319,7 +319,7 @@ private boolean isInitialized() { } public String getStoreForChangelogTopic(final String topicName) { - return builder.getChangelogTopicToStore().get(topicName); + return topologyMetadata.getStoreForChangelogTopic(topicName); } private class SourceTopicsInfo { diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamsPartitionAssignor.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamsPartitionAssignor.java index 83451422e0c59..2f396b564989a 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamsPartitionAssignor.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamsPartitionAssignor.java @@ -370,7 +370,8 @@ public GroupAssignment assign(final Cluster metadata, final GroupSubscription gr // construct the assignment of tasks to clients - final Map topicGroups = taskManager.builder().topicGroups(); + final Map topicGroups = taskManager.topologyMetadata().topicGroups(); + final Set allSourceTopics = new HashSet<>(); final Map> sourceTopicsByGroup = new HashMap<>(); for (final Map.Entry entry : topicGroups.entrySet()) { @@ -476,7 +477,7 @@ private boolean checkMetadataVersions(final int minReceivedMetadataVersion, private Map prepareRepartitionTopics(final Cluster metadata) { final RepartitionTopics repartitionTopics = new RepartitionTopics( - taskManager.builder(), + taskManager.topologyMetadata(), internalTopicManager, copartitionedTopicsEnforcer, metadata, diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/TaskManager.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/TaskManager.java index 7f32526683eaf..509c882d12483 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/TaskManager.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/TaskManager.java @@ -79,7 +79,7 @@ public class TaskManager { private final ChangelogReader changelogReader; private final UUID processId; private final String logPrefix; - private final InternalTopologyBuilder builder; + private final TopologyMetadata topologyMetadata; private final Admin adminClient; private final StateDirectory stateDirectory; private final StreamThread.ProcessingMode processingMode; @@ -101,7 +101,7 @@ public class TaskManager { final StreamsMetricsImpl streamsMetrics, final ActiveTaskCreator activeTaskCreator, final StandbyTaskCreator standbyTaskCreator, - final InternalTopologyBuilder builder, + final TopologyMetadata topologyMetadata, final Admin adminClient, final StateDirectory stateDirectory, final StreamThread.ProcessingMode processingMode) { @@ -109,11 +109,11 @@ public class TaskManager { this.changelogReader = changelogReader; this.processId = processId; this.logPrefix = logPrefix; - this.builder = builder; + this.topologyMetadata = topologyMetadata; this.adminClient = adminClient; this.stateDirectory = stateDirectory; this.processingMode = processingMode; - this.tasks = new Tasks(logPrefix, builder, streamsMetrics, activeTaskCreator, standbyTaskCreator); + this.tasks = new Tasks(logPrefix, topologyMetadata, streamsMetrics, activeTaskCreator, standbyTaskCreator); final LogContext logContext = new LogContext(logPrefix); log = logContext.logger(getClass()); @@ -128,8 +128,8 @@ public UUID processId() { return processId; } - InternalTopologyBuilder builder() { - return builder; + public TopologyMetadata topologyMetadata() { + return topologyMetadata; } boolean isRebalanceInProgress() { @@ -137,7 +137,7 @@ boolean isRebalanceInProgress() { } void handleRebalanceStart(final Set subscribedTopics) { - builder.addSubscribedTopicsFromMetadata(subscribedTopics, logPrefix); + topologyMetadata.addSubscribedTopicsFromMetadata(subscribedTopics, logPrefix); tryToLockAllNonEmptyTaskDirectories(); @@ -267,7 +267,7 @@ public void handleAssignment(final Map> activeTasks, "\tExisting standby tasks: {}", activeTasks.keySet(), standbyTasks.keySet(), activeTaskIds(), standbyTaskIds()); - builder.addSubscribedTopicsFromAssignment( + topologyMetadata.addSubscribedTopicsFromAssignment( activeTasks.values().stream().flatMap(Collection::stream).collect(Collectors.toList()), logPrefix ); diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/Tasks.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/Tasks.java index 4193deb6f7d70..2f1394535e17c 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/Tasks.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/Tasks.java @@ -37,7 +37,7 @@ class Tasks { private final Logger log; - private final InternalTopologyBuilder builder; + private final TopologyMetadata topologyMetadata; private final StreamsMetricsImpl streamsMetrics; private final Map allTasksPerId = new TreeMap<>(); @@ -66,7 +66,7 @@ class Tasks { private Consumer mainConsumer; Tasks(final String logPrefix, - final InternalTopologyBuilder builder, + final TopologyMetadata topologyMetadata, final StreamsMetricsImpl streamsMetrics, final ActiveTaskCreator activeTaskCreator, final StandbyTaskCreator standbyTaskCreator) { @@ -74,7 +74,7 @@ class Tasks { final LogContext logContext = new LogContext(logPrefix); log = logContext.logger(getClass()); - this.builder = builder; + this.topologyMetadata = topologyMetadata; this.streamsMetrics = streamsMetrics; this.activeTaskCreator = activeTaskCreator; this.standbyTaskCreator = standbyTaskCreator; @@ -168,7 +168,7 @@ void updateInputPartitionsAndResume(final Task task, final Set t activeTasksPerPartition.put(topicPartition, task); } } - task.updateInputPartitions(topicPartitions, builder.nodeToSourceTopics()); + task.updateInputPartitions(topicPartitions, topologyMetadata.nodeToSourceTopics(task.id())); } task.resume(); } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/TopologyMetadata.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/TopologyMetadata.java index 2b7392ab5bbb7..b695da3ffcc5b 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/TopologyMetadata.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/TopologyMetadata.java @@ -16,10 +16,295 @@ */ package org.apache.kafka.streams.processor.internals; +import org.apache.kafka.clients.consumer.OffsetResetStrategy; +import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.streams.StreamsConfig; +import org.apache.kafka.streams.errors.TopologyException; +import org.apache.kafka.streams.processor.StateStore; +import org.apache.kafka.streams.processor.TaskId; +import org.apache.kafka.streams.processor.internals.InternalTopologyBuilder.TopicsInfo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO KAFKA-12648: +// 1) synchronize on these methods instead of individual InternalTopologyBuilder methods, +// where applicable (ie not used elsewhere in potentially thread-unsafe way) public class TopologyMetadata { - //TODO KAFKA-12648: the TopologyMetadata class is filled in by Pt. 2 (PR #10683) + private final Logger log = LoggerFactory.getLogger(TopologyMetadata.class); + + // the '_' character is not allowed for topology names, thus it's safe to use to indicate that it's not a named topology + private static final String UNNAMED_TOPOLOGY = "__UNNAMED_TOPOLOGY__"; + private static final Pattern EMPTY_ZERO_LENGTH_PATTERN = Pattern.compile(""); + + private final StreamsConfig config; + private final SortedMap builders; // Keep sorted by topology name for readability + + private ProcessorTopology globalTopology; + private Map globalStateStores = new HashMap<>(); + final Set allInputTopics = new HashSet<>(); + + public TopologyMetadata(final InternalTopologyBuilder builder, final StreamsConfig config) { + this.config = config; + builders = new TreeMap<>(); + if (builder.hasNamedTopology()) { + builders.put(builder.namedTopology(), builder); + } else { + builders.put(UNNAMED_TOPOLOGY, builder); + } + } + + public TopologyMetadata(final SortedMap builders, final StreamsConfig config) { + this.config = config; + this.builders = builders; + if (builders.isEmpty()) { + log.debug("Building KafkaStreams app with no empty topology"); + } + } + + public int getNumStreamThreads(final StreamsConfig config) { + final int configuredNumStreamThreads = config.getInt(StreamsConfig.NUM_STREAM_THREADS_CONFIG); + + // If the application uses named topologies, it's possible to start up with no topologies at all and only add them later + if (builders.isEmpty()) { + if (configuredNumStreamThreads != 0) { + log.info("Overriding number of StreamThreads to zero for empty topology"); + } + return 0; + } + + // If there are topologies but they are all empty, this indicates a bug in user code + if (hasNoNonGlobalTopology() && !hasGlobalTopology()) { + log.error("Topology with no input topics will create no stream threads and no global thread."); + throw new TopologyException("Topology has no stream threads and no global threads, " + + "must subscribe to at least one source topic or global table."); + } + + // Lastly we check for an empty non-global topology and override the threads to zero if set otherwise + if (configuredNumStreamThreads != 0 && hasNoNonGlobalTopology()) { + log.info("Overriding number of StreamThreads to zero for global-only topology"); + return 0; + } + + return configuredNumStreamThreads; + } + + public boolean hasNamedTopologies() { + // This includes the case of starting up with no named topologies at all + return !builders.containsKey(UNNAMED_TOPOLOGY); + } + + public boolean hasGlobalTopology() { + return evaluateConditionIsTrueForAnyBuilders(InternalTopologyBuilder::hasGlobalStores); + } + + public boolean hasNoNonGlobalTopology() { + return evaluateConditionIsTrueForAnyBuilders(InternalTopologyBuilder::hasNoNonGlobalTopology); + } + + public boolean hasPersistentStores() { + // If the app is using named topologies, there may not be any persistent state when it first starts up + // but a new NamedTopology may introduce it later, so we must return true + if (hasNamedTopologies()) { + return true; + } + return evaluateConditionIsTrueForAnyBuilders(InternalTopologyBuilder::hasPersistentStores); + } + + public boolean hasStore(final String name) { + return evaluateConditionIsTrueForAnyBuilders(b -> b.hasStore(name)); + } + + public boolean hasOffsetResetOverrides() { + return evaluateConditionIsTrueForAnyBuilders(InternalTopologyBuilder::hasOffsetResetOverrides); + } + + public OffsetResetStrategy offsetResetStrategy(final String topic) { + for (final InternalTopologyBuilder builder : builders.values()) { + final OffsetResetStrategy resetStrategy = builder.offsetResetStrategy(topic); + if (resetStrategy != null) { + return resetStrategy; + } + } + return null; + } + + Collection sourceTopicCollection() { + final List sourceTopics = new ArrayList<>(); + applyToEachBuilder(b -> sourceTopics.addAll(b.sourceTopicCollection())); + return sourceTopics; + } + + Pattern sourceTopicPattern() { + final StringBuilder patternBuilder = new StringBuilder(); + + applyToEachBuilder(b -> { + final String patternString = b.sourceTopicsPatternString(); + if (patternString.length() > 0) { + patternBuilder.append(patternString).append("|"); + } + }); + + if (patternBuilder.length() > 0) { + patternBuilder.setLength(patternBuilder.length() - 1); + return Pattern.compile(patternBuilder.toString()); + } else { + return EMPTY_ZERO_LENGTH_PATTERN; + } + } + + public boolean usesPatternSubscription() { + return evaluateConditionIsTrueForAnyBuilders(InternalTopologyBuilder::usesPatternSubscription); + } + + // Can be empty if app is started up with no Named Topologies, in order to add them on later + public boolean isEmpty() { + return builders.isEmpty(); + } + + public String topologyDescription() { + if (isEmpty()) { + return ""; + } + final StringBuilder sb = new StringBuilder(); + + applyToEachBuilder(b -> { + sb.append(b.describe().toString()); + }); + + return sb.toString(); + } + + public final void buildAndRewriteTopology() { + applyToEachBuilder(builder -> { + builder.rewriteTopology(config); + builder.buildTopology(); + + // As we go, check each topology for overlap in the set of input topics/patterns + final int numInputTopics = allInputTopics.size(); + final List inputTopics = builder.fullSourceTopicNames(); + final Collection inputPatterns = builder.allSourcePatternStrings(); + + final int numNewInputTopics = inputTopics.size() + inputPatterns.size(); + allInputTopics.addAll(inputTopics); + allInputTopics.addAll(inputPatterns); + if (allInputTopics.size() != numInputTopics + numNewInputTopics) { + inputTopics.retainAll(allInputTopics); + inputPatterns.retainAll(allInputTopics); + inputTopics.addAll(inputPatterns); + log.error("Tried to add the NamedTopology {} but it had overlap with other input topics: {}", builder.namedTopology(), inputTopics); + throw new TopologyException("Named Topologies may not subscribe to the same input topics or patterns"); + } + + final ProcessorTopology globalTopology = builder.buildGlobalStateTopology(); + if (globalTopology != null) { + if (builder.namedTopology() != null) { + throw new IllegalStateException("Global state stores are not supported with Named Topologies"); + } else if (this.globalTopology == null) { + this.globalTopology = globalTopology; + } else { + throw new IllegalStateException("Topology builder had global state, but global topology has already been set"); + } + } + globalStateStores.putAll(builder.globalStateStores()); + }); + } + + public ProcessorTopology buildSubtopology(final TaskId task) { + return lookupBuilderForTask(task).buildSubtopology(task.subtopology()); + } + + public ProcessorTopology globalTaskTopology() { + if (hasNamedTopologies()) { + throw new IllegalStateException("Global state stores are not supported with Named Topologies"); + } + return globalTopology; + } + + public Map globalStateStores() { + return globalStateStores; + } + + public Map> stateStoreNameToSourceTopics() { + final Map> stateStoreNameToSourceTopics = new HashMap<>(); + applyToEachBuilder(b -> stateStoreNameToSourceTopics.putAll(b.stateStoreNameToSourceTopics())); + return stateStoreNameToSourceTopics; + } + + public String getStoreForChangelogTopic(final String topicName) { + for (final InternalTopologyBuilder builder : builders.values()) { + final String store = builder.getStoreForChangelogTopic(topicName); + if (store != null) { + return store; + } + } + log.warn("Unable to locate any store for topic {}", topicName); + return ""; + } + + public Collection sourceTopicsForStore(final String storeName) { + final List sourceTopics = new ArrayList<>(); + applyToEachBuilder(b -> sourceTopics.addAll(b.sourceTopicsForStore(storeName))); + return sourceTopics; + } + + public Map topicGroups() { + final Map topicGroups = new HashMap<>(); + applyToEachBuilder(b -> topicGroups.putAll(b.topicGroups())); + return topicGroups; + } + + public Map> nodeToSourceTopics(final TaskId task) { + return lookupBuilderForTask(task).nodeToSourceTopics(); + } + + void addSubscribedTopicsFromMetadata(final Set topics, final String logPrefix) { + applyToEachBuilder(b -> b.addSubscribedTopicsFromMetadata(topics, logPrefix)); + } + + void addSubscribedTopicsFromAssignment(final List partitions, final String logPrefix) { + applyToEachBuilder(b -> b.addSubscribedTopicsFromAssignment(partitions, logPrefix)); + } + + public Collection> copartitionGroups() { + final List> copartitionGroups = new ArrayList<>(); + applyToEachBuilder(b -> copartitionGroups.addAll(b.copartitionGroups())); + return copartitionGroups; + } + + private InternalTopologyBuilder lookupBuilderForTask(final TaskId task) { + return task.namedTopology() == null ? builders.get(UNNAMED_TOPOLOGY) : builders.get(task.namedTopology()); + } + + private boolean evaluateConditionIsTrueForAnyBuilders(final Function condition) { + for (final InternalTopologyBuilder builder : builders.values()) { + if (condition.apply(builder)) { + return true; + } + } + return false; + } + + private void applyToEachBuilder(final Consumer function) { + for (final InternalTopologyBuilder builder : builders.values()) { + function.accept(builder); + } + } public static class Subtopology { final int nodeGroupId; diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/ProcessorNodeMetrics.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/ProcessorNodeMetrics.java index dc657ecd99d4b..5d5d530753ebf 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/ProcessorNodeMetrics.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/ProcessorNodeMetrics.java @@ -18,11 +18,9 @@ import org.apache.kafka.common.metrics.Sensor; import org.apache.kafka.common.metrics.Sensor.RecordingLevel; -import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.Version; import java.util.Map; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.LATENCY_SUFFIX; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.PROCESSOR_NODE_LEVEL_GROUP; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.RECORD_E2E_LATENCY; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.RECORD_E2E_LATENCY_AVG_DESCRIPTION; @@ -31,20 +29,14 @@ import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.ROLLUP_VALUE; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.TASK_LEVEL_GROUP; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.TOTAL_DESCRIPTION; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addAvgAndMaxToSensor; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addAvgAndMinAndMaxToSensor; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addInvocationRateAndCountToSensor; public class ProcessorNodeMetrics { private ProcessorNodeMetrics() {} - private static final String AVG_DESCRIPTION_PREFIX = "The average "; - private static final String MAX_DESCRIPTION_PREFIX = "The maximum "; private static final String RATE_DESCRIPTION_PREFIX = "The average number of "; private static final String RATE_DESCRIPTION_SUFFIX = " per second"; - private static final String LATENCY_DESCRIPTION = "latency of "; - private static final String AVG_LATENCY_DESCRIPTION_PREFIX = AVG_DESCRIPTION_PREFIX + LATENCY_DESCRIPTION; - private static final String MAX_LATENCY_DESCRIPTION_PREFIX = MAX_DESCRIPTION_PREFIX + LATENCY_DESCRIPTION; private static final String SUPPRESSION_EMIT = "suppression-emit"; private static final String SUPPRESSION_EMIT_DESCRIPTION = "emitted records from the suppression buffer"; @@ -63,33 +55,6 @@ private ProcessorNodeMetrics() {} private static final String PROCESS_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + PROCESS_DESCRIPTION; private static final String PROCESS_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + PROCESS_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; - private static final String PROCESS_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + PROCESS_DESCRIPTION; - private static final String PROCESS_MAX_LATENCY_DESCRIPTION = MAX_LATENCY_DESCRIPTION_PREFIX + PROCESS_DESCRIPTION; - - private static final String PUNCTUATE = "punctuate"; - private static final String PUNCTUATE_DESCRIPTION = "calls to punctuate"; - private static final String PUNCTUATE_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + PUNCTUATE_DESCRIPTION; - private static final String PUNCTUATE_RATE_DESCRIPTION = - RATE_DESCRIPTION_PREFIX + PUNCTUATE_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; - private static final String PUNCTUATE_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + PUNCTUATE_DESCRIPTION; - private static final String PUNCTUATE_MAX_LATENCY_DESCRIPTION = MAX_LATENCY_DESCRIPTION_PREFIX + PUNCTUATE_DESCRIPTION; - - private static final String CREATE = "create"; - private static final String CREATE_DESCRIPTION1 = "processor nodes created"; - private static final String CREATE_DESCRIPTION2 = "creations of processor nodes"; - private static final String CREATE_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + CREATE_DESCRIPTION1; - private static final String CREATE_RATE_DESCRIPTION = - RATE_DESCRIPTION_PREFIX + CREATE_DESCRIPTION1 + RATE_DESCRIPTION_SUFFIX; - private static final String CREATE_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + CREATE_DESCRIPTION2; - private static final String CREATE_MAX_LATENCY_DESCRIPTION = MAX_LATENCY_DESCRIPTION_PREFIX + CREATE_DESCRIPTION2; - - private static final String DESTROY = "destroy"; - private static final String DESTROY_DESCRIPTION = "destructions of processor nodes"; - private static final String DESTROY_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + DESTROY_DESCRIPTION; - private static final String DESTROY_RATE_DESCRIPTION = - RATE_DESCRIPTION_PREFIX + DESTROY_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; - private static final String DESTROY_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + DESTROY_DESCRIPTION; - private static final String DESTROY_MAX_LATENCY_DESCRIPTION = MAX_LATENCY_DESCRIPTION_PREFIX + DESTROY_DESCRIPTION; private static final String FORWARD = "forward"; private static final String FORWARD_DESCRIPTION = "calls to forward"; @@ -135,27 +100,6 @@ public static Sensor skippedIdempotentUpdatesSensor(final String threadId, ); } - public static Sensor processSensor(final String threadId, - final String taskId, - final String processorNodeId, - final StreamsMetricsImpl streamsMetrics) { - if (streamsMetrics.version() == Version.FROM_0100_TO_24) { - return throughputAndLatencySensorWithParent( - threadId, - taskId, - processorNodeId, - PROCESS, - PROCESS_RATE_DESCRIPTION, - PROCESS_TOTAL_DESCRIPTION, - PROCESS_AVG_LATENCY_DESCRIPTION, - PROCESS_MAX_LATENCY_DESCRIPTION, - RecordingLevel.DEBUG, - streamsMetrics - ); - } - return emptySensor(threadId, taskId, processorNodeId, PROCESS, RecordingLevel.DEBUG, streamsMetrics); - } - public static Sensor processAtSourceSensor(final String threadId, final String taskId, final String processorNodeId, @@ -182,67 +126,6 @@ public static Sensor processAtSourceSensor(final String threadId, ); } - public static Sensor punctuateSensor(final String threadId, - final String taskId, - final String processorNodeId, - final StreamsMetricsImpl streamsMetrics) { - if (streamsMetrics.version() == Version.FROM_0100_TO_24) { - return throughputAndLatencySensorWithParent( - threadId, - taskId, - processorNodeId, - PUNCTUATE, - PUNCTUATE_RATE_DESCRIPTION, - PUNCTUATE_TOTAL_DESCRIPTION, - PUNCTUATE_AVG_LATENCY_DESCRIPTION, - PUNCTUATE_MAX_LATENCY_DESCRIPTION, - RecordingLevel.DEBUG, - streamsMetrics - ); - } - return emptySensor(threadId, taskId, processorNodeId, PUNCTUATE, RecordingLevel.DEBUG, streamsMetrics); - } - - public static Sensor createSensor(final String threadId, - final String taskId, - final String processorNodeId, - final StreamsMetricsImpl streamsMetrics) { - if (streamsMetrics.version() == Version.FROM_0100_TO_24) { - return throughputAndLatencySensorWithParent( - threadId, - taskId, - processorNodeId, - CREATE, - CREATE_RATE_DESCRIPTION, - CREATE_TOTAL_DESCRIPTION, - CREATE_AVG_LATENCY_DESCRIPTION, - CREATE_MAX_LATENCY_DESCRIPTION, - RecordingLevel.DEBUG, - streamsMetrics); - } - return emptySensor(threadId, taskId, processorNodeId, CREATE, RecordingLevel.DEBUG, streamsMetrics); - } - - public static Sensor destroySensor(final String threadId, - final String taskId, - final String processorNodeId, - final StreamsMetricsImpl streamsMetrics) { - if (streamsMetrics.version() == Version.FROM_0100_TO_24) { - return throughputAndLatencySensorWithParent( - threadId, - taskId, - processorNodeId, - DESTROY, - DESTROY_RATE_DESCRIPTION, - DESTROY_TOTAL_DESCRIPTION, - DESTROY_AVG_LATENCY_DESCRIPTION, - DESTROY_MAX_LATENCY_DESCRIPTION, - RecordingLevel.DEBUG, - streamsMetrics); - } - return emptySensor(threadId, taskId, processorNodeId, DESTROY, RecordingLevel.DEBUG, streamsMetrics); - } - public static Sensor forwardSensor(final String threadId, final String taskId, final String processorNodeId, @@ -284,16 +167,6 @@ public static Sensor lateRecordDropSensor(final String threadId, streamsMetrics); } - public static Sensor processorAtSourceSensorOrForwardSensor(final String threadId, - final String taskId, - final String processorNodeId, - final StreamsMetricsImpl streamsMetrics) { - if (streamsMetrics.version() == Version.FROM_0100_TO_24) { - return forwardSensor(threadId, taskId, processorNodeId, streamsMetrics); - } - return processAtSourceSensor(threadId, taskId, processorNodeId, streamsMetrics); - } - public static Sensor e2ELatencySensor(final String threadId, final String taskId, final String processorNodeId, @@ -313,72 +186,6 @@ public static Sensor e2ELatencySensor(final String threadId, return sensor; } - private static Sensor throughputAndLatencySensorWithParent(final String threadId, - final String taskId, - final String processorNodeId, - final String metricNamePrefix, - final String descriptionOfRate, - final String descriptionOfCount, - final String descriptionOfAvgLatency, - final String descriptionOfMaxLatency, - final RecordingLevel recordingLevel, - final StreamsMetricsImpl streamsMetrics) { - final Sensor parentSensor = throughputAndLatencyParentSensor( - threadId, - taskId, - metricNamePrefix, - descriptionOfRate, - descriptionOfCount, - descriptionOfAvgLatency, - descriptionOfMaxLatency, - recordingLevel, - streamsMetrics - ); - return throughputAndLatencySensor( - threadId, - taskId, - processorNodeId, - metricNamePrefix, - descriptionOfRate, - descriptionOfCount, - descriptionOfAvgLatency, - descriptionOfMaxLatency, - recordingLevel, - streamsMetrics, - parentSensor - ); - } - - private static Sensor throughputAndLatencyParentSensor(final String threadId, - final String taskId, - final String metricNamePrefix, - final String descriptionOfRate, - final String descriptionOfCount, - final String descriptionOfAvgLatency, - final String descriptionOfMaxLatency, - final RecordingLevel recordingLevel, - final StreamsMetricsImpl streamsMetrics) { - final Sensor sensor = streamsMetrics.taskLevelSensor(threadId, taskId, metricNamePrefix, recordingLevel); - final Map parentTagMap = streamsMetrics.nodeLevelTagMap(threadId, taskId, ROLLUP_VALUE); - addAvgAndMaxToSensor( - sensor, - PROCESSOR_NODE_LEVEL_GROUP, - parentTagMap, - metricNamePrefix + LATENCY_SUFFIX, - descriptionOfAvgLatency, - descriptionOfMaxLatency - ); - addInvocationRateAndCountToSensor( - sensor, - PROCESSOR_NODE_LEVEL_GROUP, - parentTagMap, - metricNamePrefix, - descriptionOfRate, - descriptionOfCount - ); - return sensor; - } - private static Sensor throughputParentSensor(final String threadId, final String taskId, final String metricNamePrefix, @@ -421,46 +228,4 @@ private static Sensor throughputSensor(final String threadId, ); return sensor; } - - private static Sensor throughputAndLatencySensor(final String threadId, - final String taskId, - final String processorNodeId, - final String metricNamePrefix, - final String descriptionOfRate, - final String descriptionOfCount, - final String descriptionOfAvg, - final String descriptionOfMax, - final RecordingLevel recordingLevel, - final StreamsMetricsImpl streamsMetrics, - final Sensor... parentSensors) { - final Sensor sensor = - streamsMetrics.nodeLevelSensor(threadId, taskId, processorNodeId, metricNamePrefix, recordingLevel, parentSensors); - final Map tagMap = streamsMetrics.nodeLevelTagMap(threadId, taskId, processorNodeId); - addAvgAndMaxToSensor( - sensor, - PROCESSOR_NODE_LEVEL_GROUP, - tagMap, - metricNamePrefix + LATENCY_SUFFIX, - descriptionOfAvg, - descriptionOfMax - ); - addInvocationRateAndCountToSensor( - sensor, - PROCESSOR_NODE_LEVEL_GROUP, - tagMap, - metricNamePrefix, - descriptionOfRate, - descriptionOfCount - ); - return sensor; - } - - private static Sensor emptySensor(final String threadId, - final String taskId, - final String processorNodeId, - final String metricNamePrefix, - final RecordingLevel recordingLevel, - final StreamsMetricsImpl streamsMetrics) { - return streamsMetrics.nodeLevelSensor(threadId, taskId, processorNodeId, metricNamePrefix, recordingLevel); - } } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/StreamsMetricsImpl.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/StreamsMetricsImpl.java index f251c2a65cce2..79399ebbb6177 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/StreamsMetricsImpl.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/StreamsMetricsImpl.java @@ -33,7 +33,6 @@ import org.apache.kafka.common.metrics.stats.WindowedCount; import org.apache.kafka.common.metrics.stats.WindowedSum; import org.apache.kafka.common.utils.Time; -import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.StreamsMetrics; import org.apache.kafka.streams.state.internals.metrics.RocksDBMetricsRecordingTrigger; @@ -52,8 +51,7 @@ public class StreamsMetricsImpl implements StreamsMetrics { public enum Version { - LATEST, - FROM_0100_TO_24 + LATEST } static class ImmutableMetricValue implements Gauge { @@ -114,7 +112,6 @@ public int hashCode() { public static final String CLIENT_ID_TAG = "client-id"; public static final String THREAD_ID_TAG = "thread-id"; - public static final String THREAD_ID_TAG_0100_TO_24 = "client-id"; public static final String TASK_ID_TAG = "task-id"; public static final String PROCESSOR_NODE_ID_TAG = "processor-node-id"; public static final String STORE_ID_TAG = "state-id"; @@ -137,11 +134,9 @@ public int hashCode() { public static final String GROUP_SUFFIX = "-metrics"; public static final String CLIENT_LEVEL_GROUP = GROUP_PREFIX_WO_DELIMITER + GROUP_SUFFIX; public static final String THREAD_LEVEL_GROUP = GROUP_PREFIX + "thread" + GROUP_SUFFIX; - public static final String THREAD_LEVEL_GROUP_0100_TO_24 = GROUP_PREFIX_WO_DELIMITER + GROUP_SUFFIX; public static final String TASK_LEVEL_GROUP = GROUP_PREFIX + "task" + GROUP_SUFFIX; public static final String PROCESSOR_NODE_LEVEL_GROUP = GROUP_PREFIX + "processor-node" + GROUP_SUFFIX; public static final String STATE_STORE_LEVEL_GROUP = GROUP_PREFIX + "state" + GROUP_SUFFIX; - public static final String BUFFER_LEVEL_GROUP_0100_TO_24 = GROUP_PREFIX + "buffer" + GROUP_SUFFIX; public static final String CACHE_LEVEL_GROUP = GROUP_PREFIX + "record-cache" + GROUP_SUFFIX; public static final String OPERATIONS = " operations"; @@ -168,20 +163,12 @@ public StreamsMetricsImpl(final Metrics metrics, Objects.requireNonNull(builtInMetricsVersion, "Built-in metrics version cannot be null"); this.metrics = metrics; this.clientId = clientId; - version = parseBuiltInMetricsVersion(builtInMetricsVersion); + version = Version.LATEST; rocksDBMetricsRecordingTrigger = new RocksDBMetricsRecordingTrigger(time); this.parentSensors = new HashMap<>(); } - private static Version parseBuiltInMetricsVersion(final String builtInMetricsVersion) { - if (builtInMetricsVersion.equals(StreamsConfig.METRICS_LATEST)) { - return Version.LATEST; - } else { - return Version.FROM_0100_TO_24; - } - } - public Version version() { return version; } @@ -250,7 +237,7 @@ public Map clientLevelTagMap() { public Map threadLevelTagMap(final String threadId) { final Map tagMap = new LinkedHashMap<>(); - tagMap.put(version == Version.LATEST ? THREAD_ID_TAG : THREAD_ID_TAG_0100_TO_24, threadId); + tagMap.put(THREAD_ID_TAG, threadId); return tagMap; } @@ -386,11 +373,7 @@ public Map cacheLevelTagMap(final String threadId, final String taskId, final String storeName) { final Map tagMap = new LinkedHashMap<>(); - if (version == Version.FROM_0100_TO_24) { - tagMap.put(THREAD_ID_TAG_0100_TO_24, threadId); - } else { - tagMap.put(THREAD_ID_TAG, threadId); - } + tagMap.put(THREAD_ID_TAG, threadId); tagMap.put(TASK_ID_TAG, taskId); tagMap.put(RECORD_CACHE_ID_TAG, storeName); return tagMap; diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/TaskMetrics.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/TaskMetrics.java index 07b3f4b08e631..cfa1ac6333a01 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/TaskMetrics.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/TaskMetrics.java @@ -18,8 +18,6 @@ import org.apache.kafka.common.metrics.Sensor; import org.apache.kafka.common.metrics.Sensor.RecordingLevel; -import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.Version; -import org.apache.kafka.streams.state.internals.metrics.StateStoreMetrics; import java.util.Map; @@ -89,18 +87,15 @@ private TaskMetrics() {} public static Sensor processLatencySensor(final String threadId, final String taskId, final StreamsMetricsImpl streamsMetrics) { - if (streamsMetrics.version() == Version.LATEST) { - return avgAndMaxSensor( - threadId, - taskId, - PROCESS_LATENCY, - PROCESS_AVG_LATENCY_DESCRIPTION, - PROCESS_MAX_LATENCY_DESCRIPTION, - RecordingLevel.DEBUG, - streamsMetrics - ); - } - return emptySensor(threadId, taskId, PROCESS_LATENCY, RecordingLevel.DEBUG, streamsMetrics); + return avgAndMaxSensor( + threadId, + taskId, + PROCESS_LATENCY, + PROCESS_AVG_LATENCY_DESCRIPTION, + PROCESS_MAX_LATENCY_DESCRIPTION, + RecordingLevel.DEBUG, + streamsMetrics + ); } public static Sensor activeProcessRatioSensor(final String threadId, @@ -136,20 +131,17 @@ public static Sensor activeBufferedRecordsSensor(final String threadId, public static Sensor punctuateSensor(final String threadId, final String taskId, final StreamsMetricsImpl streamsMetrics) { - if (streamsMetrics.version() == Version.LATEST) { - return invocationRateAndCountAndAvgAndMaxLatencySensor( - threadId, - taskId, - PUNCTUATE, - PUNCTUATE_RATE_DESCRIPTION, - PUNCTUATE_TOTAL_DESCRIPTION, - PUNCTUATE_AVG_LATENCY_DESCRIPTION, - PUNCTUATE_MAX_LATENCY_DESCRIPTION, - Sensor.RecordingLevel.DEBUG, - streamsMetrics - ); - } - return emptySensor(threadId, taskId, PUNCTUATE, RecordingLevel.DEBUG, streamsMetrics); + return invocationRateAndCountAndAvgAndMaxLatencySensor( + threadId, + taskId, + PUNCTUATE, + PUNCTUATE_RATE_DESCRIPTION, + PUNCTUATE_TOTAL_DESCRIPTION, + PUNCTUATE_AVG_LATENCY_DESCRIPTION, + PUNCTUATE_MAX_LATENCY_DESCRIPTION, + Sensor.RecordingLevel.DEBUG, + streamsMetrics + ); } public static Sensor commitSensor(final String threadId, @@ -212,36 +204,6 @@ public static Sensor droppedRecordsSensor(final String threadId, ); } - public static Sensor droppedRecordsSensorOrSkippedRecordsSensor(final String threadId, - final String taskId, - final StreamsMetricsImpl streamsMetrics) { - if (streamsMetrics.version() == Version.FROM_0100_TO_24) { - return ThreadMetrics.skipRecordSensor(threadId, streamsMetrics); - } - return droppedRecordsSensor(threadId, taskId, streamsMetrics); - } - - public static Sensor droppedRecordsSensorOrExpiredWindowRecordDropSensor(final String threadId, - final String taskId, - final String storeType, - final String storeName, - final StreamsMetricsImpl streamsMetrics) { - if (streamsMetrics.version() == Version.FROM_0100_TO_24) { - return StateStoreMetrics.expiredWindowRecordDropSensor(taskId, storeType, storeName, streamsMetrics); - } - return droppedRecordsSensor(threadId, taskId, streamsMetrics); - } - - public static Sensor droppedRecordsSensorOrLateRecordDropSensor(final String threadId, - final String taskId, - final String processorNodeId, - final StreamsMetricsImpl streamsMetrics) { - if (streamsMetrics.version() == Version.FROM_0100_TO_24) { - return ProcessorNodeMetrics.lateRecordDropSensor(threadId, taskId, processorNodeId, streamsMetrics); - } - return droppedRecordsSensor(threadId, taskId, streamsMetrics); - } - private static Sensor invocationRateAndCountSensor(final String threadId, final String taskId, final String metricName, @@ -313,13 +275,4 @@ private static Sensor invocationRateAndCountAndAvgAndMaxLatencySensor(final Stri ); return sensor; } - - private static Sensor emptySensor(final String threadId, - final String taskId, - final String metricName, - final RecordingLevel recordingLevel, - final StreamsMetricsImpl streamsMetrics, - final Sensor... parentSensors) { - return streamsMetrics.taskLevelSensor(threadId, taskId, metricName, recordingLevel, parentSensors); - } } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/ThreadMetrics.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/ThreadMetrics.java index d32c3014c9f70..28cb10f09f593 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/ThreadMetrics.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/metrics/ThreadMetrics.java @@ -18,7 +18,6 @@ import org.apache.kafka.common.metrics.Sensor; import org.apache.kafka.common.metrics.Sensor.RecordingLevel; -import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.Version; import java.util.Map; @@ -30,7 +29,6 @@ import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.ROLLUP_VALUE; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.TASK_LEVEL_GROUP; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.THREAD_LEVEL_GROUP; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.THREAD_LEVEL_GROUP_0100_TO_24; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.TOTAL_DESCRIPTION; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addAvgAndMaxToSensor; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addInvocationRateAndCountToSensor; @@ -163,10 +161,9 @@ public static Sensor processLatencySensor(final String threadId, final Sensor sensor = streamsMetrics.threadLevelSensor(threadId, PROCESS + LATENCY_SUFFIX, RecordingLevel.INFO); final Map tagMap = streamsMetrics.threadLevelTagMap(threadId); - final String threadLevelGroup = threadLevelGroup(streamsMetrics); addAvgAndMaxToSensor( sensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, PROCESS + LATENCY_SUFFIX, PROCESS_AVG_LATENCY_DESCRIPTION, @@ -180,10 +177,9 @@ public static Sensor pollRecordsSensor(final String threadId, final Sensor sensor = streamsMetrics.threadLevelSensor(threadId, POLL + RECORDS_SUFFIX, RecordingLevel.INFO); final Map tagMap = streamsMetrics.threadLevelTagMap(threadId); - final String threadLevelGroup = threadLevelGroup(streamsMetrics); addAvgAndMaxToSensor( sensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, POLL + RECORDS_SUFFIX, POLL_AVG_RECORDS_DESCRIPTION, @@ -197,10 +193,9 @@ public static Sensor processRecordsSensor(final String threadId, final Sensor sensor = streamsMetrics.threadLevelSensor(threadId, PROCESS + RECORDS_SUFFIX, RecordingLevel.INFO); final Map tagMap = streamsMetrics.threadLevelTagMap(threadId); - final String threadLevelGroup = threadLevelGroup(streamsMetrics); addAvgAndMaxToSensor( sensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, PROCESS + RECORDS_SUFFIX, PROCESS_AVG_RECORDS_DESCRIPTION, @@ -214,10 +209,9 @@ public static Sensor processRateSensor(final String threadId, final Sensor sensor = streamsMetrics.threadLevelSensor(threadId, PROCESS + RATE_SUFFIX, RecordingLevel.INFO); final Map tagMap = streamsMetrics.threadLevelTagMap(threadId); - final String threadLevelGroup = threadLevelGroup(streamsMetrics); addRateOfSumAndSumMetricsToSensor( sensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, PROCESS, PROCESS_RATE_DESCRIPTION, @@ -263,7 +257,7 @@ public static Sensor processRatioSensor(final String threadId, final Map tagMap = streamsMetrics.threadLevelTagMap(threadId); addValueMetricToSensor( sensor, - threadLevelGroup(streamsMetrics), + THREAD_LEVEL_GROUP, tagMap, PROCESS + RATIO_SUFFIX, PROCESS_RATIO_DESCRIPTION @@ -278,7 +272,7 @@ public static Sensor punctuateRatioSensor(final String threadId, final Map tagMap = streamsMetrics.threadLevelTagMap(threadId); addValueMetricToSensor( sensor, - threadLevelGroup(streamsMetrics), + THREAD_LEVEL_GROUP, tagMap, PUNCTUATE + RATIO_SUFFIX, PUNCTUATE_RATIO_DESCRIPTION @@ -293,7 +287,7 @@ public static Sensor pollRatioSensor(final String threadId, final Map tagMap = streamsMetrics.threadLevelTagMap(threadId); addValueMetricToSensor( sensor, - threadLevelGroup(streamsMetrics), + THREAD_LEVEL_GROUP, tagMap, POLL + RATIO_SUFFIX, POLL_RATIO_DESCRIPTION @@ -308,7 +302,7 @@ public static Sensor commitRatioSensor(final String threadId, final Map tagMap = streamsMetrics.threadLevelTagMap(threadId); addValueMetricToSensor( sensor, - threadLevelGroup(streamsMetrics), + THREAD_LEVEL_GROUP, tagMap, COMMIT + RATIO_SUFFIX, COMMIT_RATIO_DESCRIPTION @@ -325,7 +319,7 @@ private static Sensor invocationRateAndCountSensor(final String threadId, final Sensor sensor = streamsMetrics.threadLevelSensor(threadId, metricName, recordingLevel); addInvocationRateAndCountToSensor( sensor, - threadLevelGroup(streamsMetrics), + THREAD_LEVEL_GROUP, streamsMetrics.threadLevelTagMap(threadId), metricName, descriptionOfRate, @@ -344,10 +338,9 @@ private static Sensor invocationRateAndCountAndAvgAndMaxLatencySensor(final Stri final StreamsMetricsImpl streamsMetrics) { final Sensor sensor = streamsMetrics.threadLevelSensor(threadId, metricName, recordingLevel); final Map tagMap = streamsMetrics.threadLevelTagMap(threadId); - final String threadLevelGroup = threadLevelGroup(streamsMetrics); addAvgAndMaxToSensor( sensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, metricName + LATENCY_SUFFIX, descriptionOfAvg, @@ -355,7 +348,7 @@ private static Sensor invocationRateAndCountAndAvgAndMaxLatencySensor(final Stri ); addInvocationRateAndCountToSensor( sensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, metricName, descriptionOfRate, @@ -363,8 +356,4 @@ private static Sensor invocationRateAndCountAndAvgAndMaxLatencySensor(final Stri ); return sensor; } - - private static String threadLevelGroup(final StreamsMetricsImpl streamsMetrics) { - return streamsMetrics.version() == Version.LATEST ? THREAD_LEVEL_GROUP : THREAD_LEVEL_GROUP_0100_TO_24; - } } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/KafkaStreamsNamedTopologyWrapper.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/KafkaStreamsNamedTopologyWrapper.java index e11a78fbf34e8..c5021e1763d22 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/KafkaStreamsNamedTopologyWrapper.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/KafkaStreamsNamedTopologyWrapper.java @@ -16,27 +16,98 @@ */ package org.apache.kafka.streams.processor.internals.namedtopology; +import org.apache.kafka.common.annotation.InterfaceStability.Unstable; import org.apache.kafka.streams.KafkaClientSupplier; import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.StreamsConfig; +import org.apache.kafka.streams.errors.TopologyException; +import org.apache.kafka.streams.processor.internals.TopologyMetadata; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; +import java.util.TreeMap; +import java.util.stream.Collectors; +/** + * This is currently an internal and experimental feature for enabling certain kinds of topology upgrades. Use at + * your own risk. + * + * Status: basic architecture implemented but no actual upgrades are supported yet + * + * Note: some standard features of Kafka Streams are not yet supported with NamedTopologies. These include: + * - global state stores + * - interactive queries (IQ) + * - TopologyTestDriver (TTD) + */ +@Unstable public class KafkaStreamsNamedTopologyWrapper extends KafkaStreams { - //TODO It should be possible to start up streams with no NamedTopology (or regular Topology) at all, in the meantime we can just pass in an empty NamedTopology + final Map nameToTopology = new HashMap<>(); + + /** + * A Kafka Streams application with a single initial NamedTopology + */ public KafkaStreamsNamedTopologyWrapper(final NamedTopology topology, final Properties props, final KafkaClientSupplier clientSupplier) { - super(topology, props, clientSupplier); + this(Collections.singleton(topology), new StreamsConfig(props), clientSupplier); + } + + /** + * An empty Kafka Streams application that allows NamedTopologies to be added at a later point + */ + public KafkaStreamsNamedTopologyWrapper(final Properties props, final KafkaClientSupplier clientSupplier) { + this(Collections.emptyList(), new StreamsConfig(props), clientSupplier); + } + + /** + * A Kafka Streams application with a multiple initial NamedTopologies + * + * @throws IllegalArgumentException if any of the named topologies have the same name + * @throws TopologyException if multiple NamedTopologies subscribe to the same input topics or pattern + */ + public KafkaStreamsNamedTopologyWrapper(final Collection topologies, final Properties props, final KafkaClientSupplier clientSupplier) { + this(topologies, new StreamsConfig(props), clientSupplier); + } + + private KafkaStreamsNamedTopologyWrapper(final Collection topologies, final StreamsConfig config, final KafkaClientSupplier clientSupplier) { + super( + new TopologyMetadata( + topologies.stream().collect(Collectors.toMap( + NamedTopology::name, + NamedTopology::internalTopologyBuilder, + (v1, v2) -> { + throw new IllegalArgumentException("Topology names must be unique"); + }, + () -> new TreeMap<>())), + config), + config, + clientSupplier + ); + for (final NamedTopology topology : topologies) { + nameToTopology.put(topology.name(), topology); + } } public NamedTopology getTopologyByName(final String name) { - throw new UnsupportedOperationException(); + if (nameToTopology.containsKey(name)) { + return nameToTopology.get(name); + } else { + throw new IllegalArgumentException("Unable to locate a NamedTopology called " + name); + } } public void addNamedTopology(final NamedTopology topology) { + nameToTopology.put(topology.name(), topology); throw new UnsupportedOperationException(); } - public void removeNamedTopology(final NamedTopology topology) { + public void removeNamedTopology(final String namedTopology) { throw new UnsupportedOperationException(); } + + public String getFullTopologyDescription() { + return topologyMetadata.topologyDescription(); + } } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/NamedTopology.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/NamedTopology.java index c1588a930bdf8..17138d8727336 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/NamedTopology.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/NamedTopology.java @@ -17,6 +17,8 @@ package org.apache.kafka.streams.processor.internals.namedtopology; import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.processor.internals.InternalTopologyBuilder; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +35,7 @@ void setTopologyName(final String newTopologyName) { throw new IllegalStateException("Tried to set topologyName but the name was already set"); } name = newTopologyName; + internalTopologyBuilder.setTopologyName(name); } public String name() { @@ -42,4 +45,8 @@ public String name() { public List sourceTopics() { return super.internalTopologyBuilder.fullSourceTopicNames(); } + + InternalTopologyBuilder internalTopologyBuilder() { + return internalTopologyBuilder; + } } diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/NamedTopologyStreamsBuilder.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/NamedTopologyStreamsBuilder.java index 291ece7c49a2a..f1f9af5337928 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/NamedTopologyStreamsBuilder.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/namedtopology/NamedTopologyStreamsBuilder.java @@ -22,12 +22,18 @@ import java.util.Properties; public class NamedTopologyStreamsBuilder extends StreamsBuilder { - final String topologyName; + /** + * @param topologyName any string representing your NamedTopology, all characters allowed except for '_' + * @throws IllegalArgumentException if the name contains the character '_' + */ public NamedTopologyStreamsBuilder(final String topologyName) { super(); this.topologyName = topologyName; + if (topologyName.contains("_")) { + throw new IllegalArgumentException("The character '_' is not allowed in a NamedTopology, please select a new name"); + } } public synchronized NamedTopology buildNamedTopology(final Properties props) { diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/AbstractRocksDBSegmentedBytesStore.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/AbstractRocksDBSegmentedBytesStore.java index 51fb3ca2fd7b3..f7aef116a91ca 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/AbstractRocksDBSegmentedBytesStore.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/AbstractRocksDBSegmentedBytesStore.java @@ -244,11 +244,9 @@ public void init(final ProcessorContext context, final String threadId = Thread.currentThread().getName(); final String taskName = context.taskId().toString(); - expiredRecordSensor = TaskMetrics.droppedRecordsSensorOrExpiredWindowRecordDropSensor( + expiredRecordSensor = TaskMetrics.droppedRecordsSensor( threadId, taskName, - metricScope, - name(), metrics ); diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/CacheFlushListener.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/CacheFlushListener.java index 7e5f11ae28355..c86d216386c0f 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/CacheFlushListener.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/CacheFlushListener.java @@ -16,6 +16,9 @@ */ package org.apache.kafka.streams.state.internals; +import org.apache.kafka.streams.kstream.internals.Change; +import org.apache.kafka.streams.processor.api.Record; + /** * Listen to cache flush events * @param key type @@ -31,4 +34,9 @@ public interface CacheFlushListener { * @param timestamp timestamp of new value */ void apply(final K key, final V newValue, final V oldValue, final long timestamp); + + /** + * Called when records are flushed from the {@link ThreadCache} + */ + void apply(final Record> record); } diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/CachingWindowStore.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/CachingWindowStore.java index ad5e92cec7112..634ea820106ab 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/CachingWindowStore.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/CachingWindowStore.java @@ -84,7 +84,7 @@ public void init(final StateStoreContext context, final StateStore root) { private void initInternal(final InternalProcessorContext context) { this.context = context; - final String topic = ProcessorStateManager.storeChangelogTopic(context.applicationId(), name()); + final String topic = ProcessorStateManager.storeChangelogTopic(context.applicationId(), name(), context.taskId().namedTopology()); bytesSerdes = new StateSerdes<>( topic, diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemorySessionStore.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemorySessionStore.java index affd47ac14292..722ed43ff0cae 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemorySessionStore.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemorySessionStore.java @@ -83,11 +83,9 @@ public void init(final ProcessorContext context, final StateStore root) { if (context instanceof InternalProcessorContext) { this.context = (InternalProcessorContext) context; final StreamsMetricsImpl metrics = this.context.metrics(); - expiredRecordSensor = TaskMetrics.droppedRecordsSensorOrExpiredWindowRecordDropSensor( + expiredRecordSensor = TaskMetrics.droppedRecordsSensor( threadId, taskName, - metricScope, - name, metrics ); } else { diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemoryTimeOrderedKeyValueBuffer.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemoryTimeOrderedKeyValueBuffer.java index 8d0a2a876f9f7..adecc7dc69fc7 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemoryTimeOrderedKeyValueBuffer.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemoryTimeOrderedKeyValueBuffer.java @@ -90,7 +90,6 @@ public final class InMemoryTimeOrderedKeyValueBuffer implements TimeOrdere private Sensor bufferSizeSensor; private Sensor bufferCountSensor; private StreamsMetricsImpl streamsMetrics; - private String threadId; private String taskId; private volatile boolean open; @@ -212,16 +211,13 @@ private void init(final StateStore root) { taskId = context.taskId().toString(); streamsMetrics = context.metrics(); - threadId = Thread.currentThread().getName(); bufferSizeSensor = StateStoreMetrics.suppressionBufferSizeSensor( - threadId, taskId, METRIC_SCOPE, storeName, streamsMetrics ); bufferCountSensor = StateStoreMetrics.suppressionBufferCountSensor( - threadId, taskId, METRIC_SCOPE, storeName, @@ -229,7 +225,7 @@ private void init(final StateStore root) { ); context.register(root, (RecordBatchingStateRestoreCallback) this::restoreBatch); - changelogTopic = ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName); + changelogTopic = ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName, context.taskId().namedTopology()); updateBufferMetrics(); open = true; partition = context.taskId().partition(); diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemoryWindowStore.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemoryWindowStore.java index 5dbfc0dfe02bd..7c0d21c7c7d18 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemoryWindowStore.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemoryWindowStore.java @@ -93,11 +93,9 @@ public void init(final ProcessorContext context, final StateStore root) { final StreamsMetricsImpl metrics = ProcessorContextUtils.getMetricsImpl(context); final String threadId = Thread.currentThread().getName(); final String taskName = context.taskId().toString(); - expiredRecordSensor = TaskMetrics.droppedRecordsSensorOrExpiredWindowRecordDropSensor( + expiredRecordSensor = TaskMetrics.droppedRecordsSensor( threadId, taskName, - metricScope, - name, metrics ); diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredKeyValueStore.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredKeyValueStore.java index 18c44e8a49faa..d7aff04742ba5 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredKeyValueStore.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredKeyValueStore.java @@ -23,10 +23,13 @@ import org.apache.kafka.common.utils.Time; import org.apache.kafka.streams.KeyValue; import org.apache.kafka.streams.errors.ProcessorStateException; +import org.apache.kafka.streams.kstream.internals.Change; import org.apache.kafka.streams.kstream.internals.WrappingNullableUtils; import org.apache.kafka.streams.processor.ProcessorContext; import org.apache.kafka.streams.processor.StateStore; import org.apache.kafka.streams.processor.StateStoreContext; +import org.apache.kafka.streams.processor.TaskId; +import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.processor.internals.InternalProcessorContext; import org.apache.kafka.streams.processor.internals.ProcessorContextUtils; import org.apache.kafka.streams.processor.internals.ProcessorStateManager; @@ -74,8 +77,7 @@ public class MeteredKeyValueStore private Sensor e2eLatencySensor; private InternalProcessorContext context; private StreamsMetricsImpl streamsMetrics; - private final String threadId; - private String taskId; + private TaskId taskId; MeteredKeyValueStore(final KeyValueStore inner, final String metricsScope, @@ -84,7 +86,6 @@ public class MeteredKeyValueStore final Serde valueSerde) { super(inner); this.metricsScope = metricsScope; - threadId = Thread.currentThread().getName(); this.time = time != null ? time : Time.SYSTEM; this.keySerde = keySerde; this.valueSerde = valueSerde; @@ -95,13 +96,13 @@ public class MeteredKeyValueStore public void init(final ProcessorContext context, final StateStore root) { this.context = context instanceof InternalProcessorContext ? (InternalProcessorContext) context : null; - taskId = context.taskId().toString(); + taskId = context.taskId(); initStoreSerde(context); streamsMetrics = (StreamsMetricsImpl) context.metrics(); registerMetrics(); final Sensor restoreSensor = - StateStoreMetrics.restoreSensor(threadId, taskId, metricsScope, name(), streamsMetrics); + StateStoreMetrics.restoreSensor(taskId.toString(), metricsScope, name(), streamsMetrics); // register and possibly restore the state from the logs maybeMeasureLatency(() -> super.init(context, root), time, restoreSensor); @@ -111,29 +112,29 @@ public void init(final ProcessorContext context, public void init(final StateStoreContext context, final StateStore root) { this.context = context instanceof InternalProcessorContext ? (InternalProcessorContext) context : null; - taskId = context.taskId().toString(); + taskId = context.taskId(); initStoreSerde(context); streamsMetrics = (StreamsMetricsImpl) context.metrics(); registerMetrics(); final Sensor restoreSensor = - StateStoreMetrics.restoreSensor(threadId, taskId, metricsScope, name(), streamsMetrics); + StateStoreMetrics.restoreSensor(taskId.toString(), metricsScope, name(), streamsMetrics); // register and possibly restore the state from the logs maybeMeasureLatency(() -> super.init(context, root), time, restoreSensor); } private void registerMetrics() { - putSensor = StateStoreMetrics.putSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - putIfAbsentSensor = StateStoreMetrics.putIfAbsentSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - putAllSensor = StateStoreMetrics.putAllSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - getSensor = StateStoreMetrics.getSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - allSensor = StateStoreMetrics.allSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - rangeSensor = StateStoreMetrics.rangeSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - prefixScanSensor = StateStoreMetrics.prefixScanSensor(taskId, metricsScope, name(), streamsMetrics); - flushSensor = StateStoreMetrics.flushSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - deleteSensor = StateStoreMetrics.deleteSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - e2eLatencySensor = StateStoreMetrics.e2ELatencySensor(taskId, metricsScope, name(), streamsMetrics); + putSensor = StateStoreMetrics.putSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + putIfAbsentSensor = StateStoreMetrics.putIfAbsentSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + putAllSensor = StateStoreMetrics.putAllSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + getSensor = StateStoreMetrics.getSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + allSensor = StateStoreMetrics.allSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + rangeSensor = StateStoreMetrics.rangeSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + prefixScanSensor = StateStoreMetrics.prefixScanSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + flushSensor = StateStoreMetrics.flushSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + deleteSensor = StateStoreMetrics.deleteSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + e2eLatencySensor = StateStoreMetrics.e2ELatencySensor(taskId.toString(), metricsScope, name(), streamsMetrics); } protected Serde prepareValueSerdeForStore(final Serde valueSerde, final Serde contextKeySerde, final Serde contextValueSerde) { @@ -148,7 +149,7 @@ private void initStoreSerde(final ProcessorContext context) { serdes = new StateSerdes<>( changelogTopic != null ? changelogTopic : - ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName), + ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName, taskId.namedTopology()), prepareKeySerde(keySerde, context.keySerde(), context.valueSerde()), prepareValueSerdeForStore(valueSerde, context.keySerde(), context.valueSerde()) ); @@ -160,7 +161,7 @@ private void initStoreSerde(final StateStoreContext context) { serdes = new StateSerdes<>( changelogTopic != null ? changelogTopic : - ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName), + ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName, taskId.namedTopology()), prepareKeySerde(keySerde, context.keySerde(), context.valueSerde()), prepareValueSerdeForStore(valueSerde, context.keySerde(), context.valueSerde()) ); @@ -173,12 +174,28 @@ public boolean setFlushListener(final CacheFlushListener listener, final KeyValueStore wrapped = wrapped(); if (wrapped instanceof CachedStateStore) { return ((CachedStateStore) wrapped).setFlushListener( - (rawKey, rawNewValue, rawOldValue, timestamp) -> listener.apply( - serdes.keyFrom(rawKey), - rawNewValue != null ? serdes.valueFrom(rawNewValue) : null, - rawOldValue != null ? serdes.valueFrom(rawOldValue) : null, - timestamp - ), + new CacheFlushListener() { + @Override + public void apply(final byte[] rawKey, final byte[] rawNewValue, final byte[] rawOldValue, final long timestamp) { + listener.apply( + serdes.keyFrom(rawKey), + rawNewValue != null ? serdes.valueFrom(rawNewValue) : null, + rawOldValue != null ? serdes.valueFrom(rawOldValue) : null, + timestamp + ); + } + + @Override + public void apply(final Record> record) { + listener.apply( + record.withKey(serdes.keyFrom(record.key())) + .withValue(new Change<>( + record.value().newValue != null ? serdes.valueFrom(record.value().newValue) : null, + record.value().oldValue != null ? serdes.valueFrom(record.value().oldValue) : null + )) + ); + } + }, sendOldValues); } return false; @@ -292,7 +309,7 @@ public void close() { try { wrapped().close(); } finally { - streamsMetrics.removeAllStoreLevelSensorsAndMetrics(taskId, name()); + streamsMetrics.removeAllStoreLevelSensorsAndMetrics(taskId.toString(), name()); } } diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredSessionStore.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredSessionStore.java index 1fbc8db095799..f061a68997b6d 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredSessionStore.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredSessionStore.java @@ -22,10 +22,13 @@ import org.apache.kafka.common.utils.Time; import org.apache.kafka.streams.errors.ProcessorStateException; import org.apache.kafka.streams.kstream.Windowed; +import org.apache.kafka.streams.kstream.internals.Change; import org.apache.kafka.streams.kstream.internals.WrappingNullableUtils; import org.apache.kafka.streams.processor.ProcessorContext; import org.apache.kafka.streams.processor.StateStore; import org.apache.kafka.streams.processor.StateStoreContext; +import org.apache.kafka.streams.processor.TaskId; +import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.processor.internals.InternalProcessorContext; import org.apache.kafka.streams.processor.internals.ProcessorContextUtils; import org.apache.kafka.streams.processor.internals.ProcessorStateManager; @@ -55,8 +58,8 @@ public class MeteredSessionStore private Sensor removeSensor; private Sensor e2eLatencySensor; private InternalProcessorContext context; - private final String threadId; - private String taskId; + private TaskId taskId; + MeteredSessionStore(final SessionStore inner, final String metricsScope, @@ -64,7 +67,6 @@ public class MeteredSessionStore final Serde valueSerde, final Time time) { super(inner); - threadId = Thread.currentThread().getName(); this.metricsScope = metricsScope; this.keySerde = keySerde; this.valueSerde = valueSerde; @@ -76,13 +78,13 @@ public class MeteredSessionStore public void init(final ProcessorContext context, final StateStore root) { this.context = context instanceof InternalProcessorContext ? (InternalProcessorContext) context : null; + taskId = context.taskId(); initStoreSerde(context); - taskId = context.taskId().toString(); streamsMetrics = (StreamsMetricsImpl) context.metrics(); registerMetrics(); final Sensor restoreSensor = - StateStoreMetrics.restoreSensor(threadId, taskId, metricsScope, name(), streamsMetrics); + StateStoreMetrics.restoreSensor(taskId.toString(), metricsScope, name(), streamsMetrics); // register and possibly restore the state from the logs maybeMeasureLatency(() -> super.init(context, root), time, restoreSensor); @@ -92,24 +94,25 @@ public void init(final ProcessorContext context, public void init(final StateStoreContext context, final StateStore root) { this.context = context instanceof InternalProcessorContext ? (InternalProcessorContext) context : null; + taskId = context.taskId(); initStoreSerde(context); - taskId = context.taskId().toString(); streamsMetrics = (StreamsMetricsImpl) context.metrics(); registerMetrics(); final Sensor restoreSensor = - StateStoreMetrics.restoreSensor(threadId, taskId, metricsScope, name(), streamsMetrics); + StateStoreMetrics.restoreSensor(taskId.toString(), metricsScope, name(), streamsMetrics); // register and possibly restore the state from the logs maybeMeasureLatency(() -> super.init(context, root), time, restoreSensor); } private void registerMetrics() { - putSensor = StateStoreMetrics.putSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - fetchSensor = StateStoreMetrics.fetchSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - flushSensor = StateStoreMetrics.flushSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - removeSensor = StateStoreMetrics.removeSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - e2eLatencySensor = StateStoreMetrics.e2ELatencySensor(taskId, metricsScope, name(), streamsMetrics); + + putSensor = StateStoreMetrics.putSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + fetchSensor = StateStoreMetrics.fetchSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + flushSensor = StateStoreMetrics.flushSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + removeSensor = StateStoreMetrics.removeSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + e2eLatencySensor = StateStoreMetrics.e2ELatencySensor(taskId.toString(), metricsScope, name(), streamsMetrics); } @@ -119,7 +122,7 @@ private void initStoreSerde(final ProcessorContext context) { serdes = new StateSerdes<>( changelogTopic != null ? changelogTopic : - ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName), + ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName, taskId.namedTopology()), WrappingNullableUtils.prepareKeySerde(keySerde, context.keySerde(), context.valueSerde()), WrappingNullableUtils.prepareValueSerde(valueSerde, context.keySerde(), context.valueSerde()) ); @@ -131,7 +134,7 @@ private void initStoreSerde(final StateStoreContext context) { serdes = new StateSerdes<>( changelogTopic != null ? changelogTopic : - ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName), + ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName, taskId.namedTopology()), WrappingNullableUtils.prepareKeySerde(keySerde, context.keySerde(), context.valueSerde()), WrappingNullableUtils.prepareValueSerde(valueSerde, context.keySerde(), context.valueSerde()) ); @@ -144,12 +147,28 @@ public boolean setFlushListener(final CacheFlushListener, V> listene final SessionStore wrapped = wrapped(); if (wrapped instanceof CachedStateStore) { return ((CachedStateStore) wrapped).setFlushListener( - (key, newValue, oldValue, timestamp) -> listener.apply( - SessionKeySchema.from(key, serdes.keyDeserializer(), serdes.topic()), - newValue != null ? serdes.valueFrom(newValue) : null, - oldValue != null ? serdes.valueFrom(oldValue) : null, - timestamp - ), + new CacheFlushListener() { + @Override + public void apply(final byte[] key, final byte[] newValue, final byte[] oldValue, final long timestamp) { + listener.apply( + SessionKeySchema.from(key, serdes.keyDeserializer(), serdes.topic()), + newValue != null ? serdes.valueFrom(newValue) : null, + oldValue != null ? serdes.valueFrom(oldValue) : null, + timestamp + ); + } + + @Override + public void apply(final Record> record) { + listener.apply( + record.withKey(SessionKeySchema.from(record.key(), serdes.keyDeserializer(), serdes.topic())) + .withValue(new Change<>( + record.value().newValue != null ? serdes.valueFrom(record.value().newValue) : null, + record.value().oldValue != null ? serdes.valueFrom(record.value().oldValue) : null + )) + ); + } + }, sendOldValues); } return false; @@ -360,7 +379,7 @@ public void close() { try { wrapped().close(); } finally { - streamsMetrics.removeAllStoreLevelSensorsAndMetrics(taskId, name()); + streamsMetrics.removeAllStoreLevelSensorsAndMetrics(taskId.toString(), name()); } } diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredWindowStore.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredWindowStore.java index 91b4387fbbdc5..0923f40d3d3e4 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredWindowStore.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/MeteredWindowStore.java @@ -22,10 +22,13 @@ import org.apache.kafka.common.utils.Time; import org.apache.kafka.streams.errors.ProcessorStateException; import org.apache.kafka.streams.kstream.Windowed; +import org.apache.kafka.streams.kstream.internals.Change; import org.apache.kafka.streams.kstream.internals.WrappingNullableUtils; import org.apache.kafka.streams.processor.ProcessorContext; import org.apache.kafka.streams.processor.StateStore; import org.apache.kafka.streams.processor.StateStoreContext; +import org.apache.kafka.streams.processor.TaskId; +import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.processor.internals.InternalProcessorContext; import org.apache.kafka.streams.processor.internals.ProcessorContextUtils; import org.apache.kafka.streams.processor.internals.ProcessorStateManager; @@ -57,8 +60,7 @@ public class MeteredWindowStore private Sensor flushSensor; private Sensor e2eLatencySensor; private InternalProcessorContext context; - private final String threadId; - private String taskId; + private TaskId taskId; MeteredWindowStore(final WindowStore inner, final long windowSizeMs, @@ -68,7 +70,6 @@ public class MeteredWindowStore final Serde valueSerde) { super(inner); this.windowSizeMs = windowSizeMs; - threadId = Thread.currentThread().getName(); this.metricsScope = metricsScope; this.time = time; this.keySerde = keySerde; @@ -80,13 +81,13 @@ public class MeteredWindowStore public void init(final ProcessorContext context, final StateStore root) { this.context = context instanceof InternalProcessorContext ? (InternalProcessorContext) context : null; + taskId = context.taskId(); initStoreSerde(context); streamsMetrics = (StreamsMetricsImpl) context.metrics(); - taskId = context.taskId().toString(); registerMetrics(); final Sensor restoreSensor = - StateStoreMetrics.restoreSensor(threadId, taskId, metricsScope, name(), streamsMetrics); + StateStoreMetrics.restoreSensor(taskId.toString(), metricsScope, name(), streamsMetrics); // register and possibly restore the state from the logs maybeMeasureLatency(() -> super.init(context, root), time, restoreSensor); @@ -96,13 +97,13 @@ public void init(final ProcessorContext context, public void init(final StateStoreContext context, final StateStore root) { this.context = context instanceof InternalProcessorContext ? (InternalProcessorContext) context : null; + taskId = context.taskId(); initStoreSerde(context); streamsMetrics = (StreamsMetricsImpl) context.metrics(); - taskId = context.taskId().toString(); registerMetrics(); final Sensor restoreSensor = - StateStoreMetrics.restoreSensor(threadId, taskId, metricsScope, name(), streamsMetrics); + StateStoreMetrics.restoreSensor(taskId.toString(), metricsScope, name(), streamsMetrics); // register and possibly restore the state from the logs maybeMeasureLatency(() -> super.init(context, root), time, restoreSensor); @@ -112,10 +113,10 @@ protected Serde prepareValueSerde(final Serde valueSerde, final Serde c } private void registerMetrics() { - putSensor = StateStoreMetrics.putSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - fetchSensor = StateStoreMetrics.fetchSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - flushSensor = StateStoreMetrics.flushSensor(threadId, taskId, metricsScope, name(), streamsMetrics); - e2eLatencySensor = StateStoreMetrics.e2ELatencySensor(taskId, metricsScope, name(), streamsMetrics); + putSensor = StateStoreMetrics.putSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + fetchSensor = StateStoreMetrics.fetchSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + flushSensor = StateStoreMetrics.flushSensor(taskId.toString(), metricsScope, name(), streamsMetrics); + e2eLatencySensor = StateStoreMetrics.e2ELatencySensor(taskId.toString(), metricsScope, name(), streamsMetrics); } @Deprecated @@ -125,7 +126,7 @@ private void initStoreSerde(final ProcessorContext context) { serdes = new StateSerdes<>( changelogTopic != null ? changelogTopic : - ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName), + ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName, taskId.namedTopology()), prepareKeySerde(keySerde, context.keySerde(), context.valueSerde()), prepareValueSerde(valueSerde, context.keySerde(), context.valueSerde())); } @@ -136,7 +137,7 @@ private void initStoreSerde(final StateStoreContext context) { serdes = new StateSerdes<>( changelogTopic != null ? changelogTopic : - ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName), + ProcessorStateManager.storeChangelogTopic(context.applicationId(), storeName, taskId.namedTopology()), prepareKeySerde(keySerde, context.keySerde(), context.valueSerde()), prepareValueSerde(valueSerde, context.keySerde(), context.valueSerde())); } @@ -148,12 +149,28 @@ public boolean setFlushListener(final CacheFlushListener, V> listene final WindowStore wrapped = wrapped(); if (wrapped instanceof CachedStateStore) { return ((CachedStateStore) wrapped).setFlushListener( - (key, newValue, oldValue, timestamp) -> listener.apply( - WindowKeySchema.fromStoreKey(key, windowSizeMs, serdes.keyDeserializer(), serdes.topic()), - newValue != null ? serdes.valueFrom(newValue) : null, - oldValue != null ? serdes.valueFrom(oldValue) : null, - timestamp - ), + new CacheFlushListener() { + @Override + public void apply(final byte[] key, final byte[] newValue, final byte[] oldValue, final long timestamp) { + listener.apply( + WindowKeySchema.fromStoreKey(key, windowSizeMs, serdes.keyDeserializer(), serdes.topic()), + newValue != null ? serdes.valueFrom(newValue) : null, + oldValue != null ? serdes.valueFrom(oldValue) : null, + timestamp + ); + } + + @Override + public void apply(final Record> record) { + listener.apply( + record.withKey(WindowKeySchema.fromStoreKey(record.key(), windowSizeMs, serdes.keyDeserializer(), serdes.topic())) + .withValue(new Change<>( + record.value().newValue != null ? serdes.valueFrom(record.value().newValue) : null, + record.value().oldValue != null ? serdes.valueFrom(record.value().oldValue) : null + )) + ); + } + }, sendOldValues); } return false; @@ -294,7 +311,7 @@ public void close() { try { wrapped().close(); } finally { - streamsMetrics.removeAllStoreLevelSensorsAndMetrics(taskId, name()); + streamsMetrics.removeAllStoreLevelSensorsAndMetrics(taskId.toString(), name()); } } diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/metrics/NamedCacheMetrics.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/metrics/NamedCacheMetrics.java index 37f5de9224af2..184c50faa60ad 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/metrics/NamedCacheMetrics.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/metrics/NamedCacheMetrics.java @@ -20,14 +20,11 @@ import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.CACHE_LEVEL_GROUP; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.ROLLUP_VALUE; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.Version.FROM_0100_TO_24; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addAvgAndMinAndMaxToSensor; public class NamedCacheMetrics { private NamedCacheMetrics() {} - private static final String HIT_RATIO_0100_TO_24 = "hitRatio"; private static final String HIT_RATIO = "hit-ratio"; private static final String HIT_RATIO_AVG_DESCRIPTION = "The average cache hit ratio"; private static final String HIT_RATIO_MIN_DESCRIPTION = "The minimum cache hit ratio"; @@ -41,41 +38,14 @@ public static Sensor hitRatioSensor(final StreamsMetricsImpl streamsMetrics, final Sensor hitRatioSensor; final String hitRatioName; - if (streamsMetrics.version() == FROM_0100_TO_24) { - hitRatioName = HIT_RATIO_0100_TO_24; - final Sensor taskLevelHitRatioSensor = streamsMetrics.taskLevelSensor( - threadId, - taskName, - hitRatioName, - Sensor.RecordingLevel.DEBUG - ); - addAvgAndMinAndMaxToSensor( - taskLevelHitRatioSensor, - CACHE_LEVEL_GROUP, - streamsMetrics.cacheLevelTagMap(threadId, taskName, ROLLUP_VALUE), - hitRatioName, - HIT_RATIO_AVG_DESCRIPTION, - HIT_RATIO_MIN_DESCRIPTION, - HIT_RATIO_MAX_DESCRIPTION - ); - hitRatioSensor = streamsMetrics.cacheLevelSensor( - threadId, - taskName, - storeName, - hitRatioName, - Sensor.RecordingLevel.DEBUG, - taskLevelHitRatioSensor - ); - } else { - hitRatioName = HIT_RATIO; - hitRatioSensor = streamsMetrics.cacheLevelSensor( - threadId, - taskName, - storeName, - hitRatioName, - Sensor.RecordingLevel.DEBUG - ); - } + hitRatioName = HIT_RATIO; + hitRatioSensor = streamsMetrics.cacheLevelSensor( + threadId, + taskName, + storeName, + hitRatioName, + Sensor.RecordingLevel.DEBUG + ); addAvgAndMinAndMaxToSensor( hitRatioSensor, CACHE_LEVEL_GROUP, diff --git a/streams/src/main/java/org/apache/kafka/streams/state/internals/metrics/StateStoreMetrics.java b/streams/src/main/java/org/apache/kafka/streams/state/internals/metrics/StateStoreMetrics.java index 06402f4ab48ea..360cd8d0e103f 100644 --- a/streams/src/main/java/org/apache/kafka/streams/state/internals/metrics/StateStoreMetrics.java +++ b/streams/src/main/java/org/apache/kafka/streams/state/internals/metrics/StateStoreMetrics.java @@ -19,42 +19,35 @@ import org.apache.kafka.common.metrics.Sensor; import org.apache.kafka.common.metrics.Sensor.RecordingLevel; import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl; -import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.Version; import java.util.Map; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.BUFFER_LEVEL_GROUP_0100_TO_24; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.LATENCY_SUFFIX; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.RECORD_E2E_LATENCY; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.RECORD_E2E_LATENCY_AVG_DESCRIPTION; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.RECORD_E2E_LATENCY_MAX_DESCRIPTION; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.RECORD_E2E_LATENCY_MIN_DESCRIPTION; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.ROLLUP_VALUE; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.STATE_STORE_LEVEL_GROUP; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.TOTAL_DESCRIPTION; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addAvgAndMaxToSensor; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addAvgAndMinAndMaxToSensor; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addInvocationRateAndCountToSensor; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addInvocationRateToSensor; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.addValueMetricToSensor; public class StateStoreMetrics { private StateStoreMetrics() {} private static final String AVG_DESCRIPTION_PREFIX = "The average "; private static final String MAX_DESCRIPTION_PREFIX = "The maximum "; - private static final String CURRENT_DESCRIPTION_PREFIX = "The current "; private static final String LATENCY_DESCRIPTION = "latency of "; private static final String AVG_LATENCY_DESCRIPTION_PREFIX = AVG_DESCRIPTION_PREFIX + LATENCY_DESCRIPTION; private static final String MAX_LATENCY_DESCRIPTION_PREFIX = MAX_DESCRIPTION_PREFIX + LATENCY_DESCRIPTION; private static final String RATE_DESCRIPTION_PREFIX = "The average number of "; private static final String RATE_DESCRIPTION_SUFFIX = " per second"; - private static final String CURRENT_SUFFIX = "-current"; private static final String BUFFERED_RECORDS = "buffered records"; private static final String PUT = "put"; private static final String PUT_DESCRIPTION = "calls to put"; - private static final String PUT_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + PUT_DESCRIPTION; private static final String PUT_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + PUT_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String PUT_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + PUT_DESCRIPTION; @@ -62,7 +55,6 @@ private StateStoreMetrics() {} private static final String PUT_IF_ABSENT = "put-if-absent"; private static final String PUT_IF_ABSENT_DESCRIPTION = "calls to put-if-absent"; - private static final String PUT_IF_ABSENT_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + PUT_IF_ABSENT_DESCRIPTION; private static final String PUT_IF_ABSENT_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + PUT_IF_ABSENT_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String PUT_IF_ABSENT_AVG_LATENCY_DESCRIPTION = @@ -72,7 +64,6 @@ private StateStoreMetrics() {} private static final String PUT_ALL = "put-all"; private static final String PUT_ALL_DESCRIPTION = "calls to put-all"; - private static final String PUT_ALL_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + PUT_ALL_DESCRIPTION; private static final String PUT_ALL_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + PUT_ALL_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String PUT_ALL_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + PUT_ALL_DESCRIPTION; @@ -80,7 +71,6 @@ private StateStoreMetrics() {} private static final String GET = "get"; private static final String GET_DESCRIPTION = "calls to get"; - private static final String GET_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + GET_DESCRIPTION; private static final String GET_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + GET_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String GET_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + GET_DESCRIPTION; @@ -88,7 +78,6 @@ private StateStoreMetrics() {} private static final String FETCH = "fetch"; private static final String FETCH_DESCRIPTION = "calls to fetch"; - private static final String FETCH_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + FETCH_DESCRIPTION; private static final String FETCH_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + FETCH_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String FETCH_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + FETCH_DESCRIPTION; @@ -96,7 +85,6 @@ private StateStoreMetrics() {} private static final String ALL = "all"; private static final String ALL_DESCRIPTION = "calls to all"; - private static final String ALL_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + ALL_DESCRIPTION; private static final String ALL_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + ALL_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String ALL_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + ALL_DESCRIPTION; @@ -104,7 +92,6 @@ private StateStoreMetrics() {} private static final String RANGE = "range"; private static final String RANGE_DESCRIPTION = "calls to range"; - private static final String RANGE_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + RANGE_DESCRIPTION; private static final String RANGE_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + RANGE_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String RANGE_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + RANGE_DESCRIPTION; @@ -119,7 +106,6 @@ private StateStoreMetrics() {} private static final String FLUSH = "flush"; private static final String FLUSH_DESCRIPTION = "calls to flush"; - private static final String FLUSH_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + FLUSH_DESCRIPTION; private static final String FLUSH_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + FLUSH_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String FLUSH_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + FLUSH_DESCRIPTION; @@ -127,7 +113,6 @@ private StateStoreMetrics() {} private static final String DELETE = "delete"; private static final String DELETE_DESCRIPTION = "calls to delete"; - private static final String DELETE_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + DELETE_DESCRIPTION; private static final String DELETE_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + DELETE_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String DELETE_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + DELETE_DESCRIPTION; @@ -135,7 +120,6 @@ private StateStoreMetrics() {} private static final String REMOVE = "remove"; private static final String REMOVE_DESCRIPTION = "calls to remove"; - private static final String REMOVE_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + REMOVE_DESCRIPTION; private static final String REMOVE_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + REMOVE_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String REMOVE_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + REMOVE_DESCRIPTION; @@ -143,7 +127,6 @@ private StateStoreMetrics() {} private static final String RESTORE = "restore"; private static final String RESTORE_DESCRIPTION = "restorations"; - private static final String RESTORE_TOTAL_DESCRIPTION = TOTAL_DESCRIPTION + RESTORE_DESCRIPTION; private static final String RESTORE_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + RESTORE_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; private static final String RESTORE_AVG_LATENCY_DESCRIPTION = AVG_LATENCY_DESCRIPTION_PREFIX + RESTORE_DESCRIPTION; @@ -151,8 +134,6 @@ private StateStoreMetrics() {} private static final String SUPPRESSION_BUFFER_COUNT = "suppression-buffer-count"; private static final String SUPPRESSION_BUFFER_COUNT_DESCRIPTION = "count of " + BUFFERED_RECORDS; - private static final String SUPPRESSION_BUFFER_COUNT_CURRENT_DESCRIPTION = - CURRENT_DESCRIPTION_PREFIX + SUPPRESSION_BUFFER_COUNT_DESCRIPTION; private static final String SUPPRESSION_BUFFER_COUNT_AVG_DESCRIPTION = AVG_DESCRIPTION_PREFIX + SUPPRESSION_BUFFER_COUNT_DESCRIPTION; private static final String SUPPRESSION_BUFFER_COUNT_MAX_DESCRIPTION = @@ -160,8 +141,6 @@ private StateStoreMetrics() {} private static final String SUPPRESSION_BUFFER_SIZE = "suppression-buffer-size"; private static final String SUPPRESSION_BUFFER_SIZE_DESCRIPTION = "size of " + BUFFERED_RECORDS; - private static final String SUPPRESSION_BUFFER_SIZE_CURRENT_DESCRIPTION = - CURRENT_DESCRIPTION_PREFIX + SUPPRESSION_BUFFER_SIZE_DESCRIPTION; private static final String SUPPRESSION_BUFFER_SIZE_AVG_DESCRIPTION = AVG_DESCRIPTION_PREFIX + SUPPRESSION_BUFFER_SIZE_DESCRIPTION; private static final String SUPPRESSION_BUFFER_SIZE_MAX_DESCRIPTION = @@ -174,19 +153,16 @@ private StateStoreMetrics() {} private static final String EXPIRED_WINDOW_RECORD_DROP_RATE_DESCRIPTION = RATE_DESCRIPTION_PREFIX + EXPIRED_WINDOW_RECORD_DROP_DESCRIPTION + RATE_DESCRIPTION_SUFFIX; - public static Sensor putSensor(final String threadId, - final String taskId, + public static Sensor putSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, PUT, PUT_RATE_DESCRIPTION, - PUT_TOTAL_DESCRIPTION, PUT_AVG_LATENCY_DESCRIPTION, PUT_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -194,19 +170,16 @@ public static Sensor putSensor(final String threadId, ); } - public static Sensor putIfAbsentSensor(final String threadId, - final String taskId, + public static Sensor putIfAbsentSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, PUT_IF_ABSENT, PUT_IF_ABSENT_RATE_DESCRIPTION, - PUT_IF_ABSENT_TOTAL_DESCRIPTION, PUT_IF_ABSENT_AVG_LATENCY_DESCRIPTION, PUT_IF_ABSENT_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -214,19 +187,16 @@ public static Sensor putIfAbsentSensor(final String threadId, ); } - public static Sensor putAllSensor(final String threadId, - final String taskId, + public static Sensor putAllSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, PUT_ALL, PUT_ALL_RATE_DESCRIPTION, - PUT_ALL_TOTAL_DESCRIPTION, PUT_ALL_AVG_LATENCY_DESCRIPTION, PUT_ALL_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -234,19 +204,16 @@ public static Sensor putAllSensor(final String threadId, ); } - public static Sensor getSensor(final String threadId, - final String taskId, + public static Sensor getSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, GET, GET_RATE_DESCRIPTION, - GET_TOTAL_DESCRIPTION, GET_AVG_LATENCY_DESCRIPTION, GET_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -254,19 +221,16 @@ public static Sensor getSensor(final String threadId, ); } - public static Sensor fetchSensor(final String threadId, - final String taskId, + public static Sensor fetchSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, FETCH, FETCH_RATE_DESCRIPTION, - FETCH_TOTAL_DESCRIPTION, FETCH_AVG_LATENCY_DESCRIPTION, FETCH_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -274,19 +238,16 @@ public static Sensor fetchSensor(final String threadId, ); } - public static Sensor allSensor(final String threadId, - final String taskId, + public static Sensor allSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, ALL, ALL_RATE_DESCRIPTION, - ALL_TOTAL_DESCRIPTION, ALL_AVG_LATENCY_DESCRIPTION, ALL_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -294,19 +255,16 @@ public static Sensor allSensor(final String threadId, ); } - public static Sensor rangeSensor(final String threadId, - final String taskId, + public static Sensor rangeSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, RANGE, RANGE_RATE_DESCRIPTION, - RANGE_TOTAL_DESCRIPTION, RANGE_AVG_LATENCY_DESCRIPTION, RANGE_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -341,19 +299,16 @@ public static Sensor prefixScanSensor(final String taskId, return sensor; } - public static Sensor flushSensor(final String threadId, - final String taskId, + public static Sensor flushSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, FLUSH, FLUSH_RATE_DESCRIPTION, - FLUSH_TOTAL_DESCRIPTION, FLUSH_AVG_LATENCY_DESCRIPTION, FLUSH_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -361,19 +316,16 @@ public static Sensor flushSensor(final String threadId, ); } - public static Sensor deleteSensor(final String threadId, - final String taskId, + public static Sensor deleteSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, DELETE, DELETE_RATE_DESCRIPTION, - DELETE_TOTAL_DESCRIPTION, DELETE_AVG_LATENCY_DESCRIPTION, DELETE_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -381,19 +333,16 @@ public static Sensor deleteSensor(final String threadId, ); } - public static Sensor removeSensor(final String threadId, - final String taskId, + public static Sensor removeSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, REMOVE, REMOVE_RATE_DESCRIPTION, - REMOVE_TOTAL_DESCRIPTION, REMOVE_AVG_LATENCY_DESCRIPTION, REMOVE_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -401,18 +350,15 @@ public static Sensor removeSensor(final String threadId, ); } - public static Sensor restoreSensor(final String threadId, - final String taskId, + public static Sensor restoreSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return throughputAndLatencySensor( - threadId, taskId, storeType, storeName, RESTORE, RESTORE_RATE_DESCRIPTION, - RESTORE_TOTAL_DESCRIPTION, RESTORE_AVG_LATENCY_DESCRIPTION, RESTORE_MAX_LATENCY_DESCRIPTION, RecordingLevel.DEBUG, @@ -441,18 +387,15 @@ public static Sensor expiredWindowRecordDropSensor(final String taskId, return sensor; } - public static Sensor suppressionBufferCountSensor(final String threadId, - final String taskId, + public static Sensor suppressionBufferCountSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return sizeOrCountSensor( - threadId, taskId, storeType, storeName, SUPPRESSION_BUFFER_COUNT, - SUPPRESSION_BUFFER_COUNT_CURRENT_DESCRIPTION, SUPPRESSION_BUFFER_COUNT_AVG_DESCRIPTION, SUPPRESSION_BUFFER_COUNT_MAX_DESCRIPTION, RecordingLevel.DEBUG, @@ -460,18 +403,15 @@ public static Sensor suppressionBufferCountSensor(final String threadId, ); } - public static Sensor suppressionBufferSizeSensor(final String threadId, - final String taskId, + public static Sensor suppressionBufferSizeSensor(final String taskId, final String storeType, final String storeName, final StreamsMetricsImpl streamsMetrics) { return sizeOrCountSensor( - threadId, taskId, storeType, storeName, SUPPRESSION_BUFFER_SIZE, - SUPPRESSION_BUFFER_SIZE_CURRENT_DESCRIPTION, SUPPRESSION_BUFFER_SIZE_AVG_DESCRIPTION, SUPPRESSION_BUFFER_SIZE_MAX_DESCRIPTION, RecordingLevel.DEBUG, @@ -497,80 +437,40 @@ public static Sensor e2ELatencySensor(final String taskId, return sensor; } - private static Sensor sizeOrCountSensor(final String threadId, - final String taskId, + private static Sensor sizeOrCountSensor(final String taskId, final String storeType, final String storeName, final String metricName, - final String descriptionOfCurrentValue, final String descriptionOfAvg, final String descriptionOfMax, final RecordingLevel recordingLevel, final StreamsMetricsImpl streamsMetrics) { - final Version version = streamsMetrics.version(); final Sensor sensor = streamsMetrics.storeLevelSensor(taskId, storeName, metricName, recordingLevel); final String group; final Map tagMap; - if (version == Version.FROM_0100_TO_24) { - group = BUFFER_LEVEL_GROUP_0100_TO_24; - tagMap = streamsMetrics.bufferLevelTagMap(threadId, taskId, storeName); - addValueMetricToSensor(sensor, group, tagMap, metricName + CURRENT_SUFFIX, descriptionOfCurrentValue); - - } else { - group = STATE_STORE_LEVEL_GROUP; - tagMap = streamsMetrics.storeLevelTagMap(taskId, storeType, storeName); - } + group = STATE_STORE_LEVEL_GROUP; + tagMap = streamsMetrics.storeLevelTagMap(taskId, storeType, storeName); addAvgAndMaxToSensor(sensor, group, tagMap, metricName, descriptionOfAvg, descriptionOfMax); return sensor; } - private static Sensor throughputAndLatencySensor(final String threadId, - final String taskId, + private static Sensor throughputAndLatencySensor(final String taskId, final String storeType, final String storeName, final String metricName, final String descriptionOfRate, - final String descriptionOfCount, final String descriptionOfAvg, final String descriptionOfMax, final RecordingLevel recordingLevel, final StreamsMetricsImpl streamsMetrics) { final Sensor sensor; final String latencyMetricName = metricName + LATENCY_SUFFIX; - final Version version = streamsMetrics.version(); final Map tagMap = streamsMetrics.storeLevelTagMap(taskId, storeType, storeName); - final String stateStoreLevelGroup = stateStoreLevelGroup(storeType, version); - if (version == Version.FROM_0100_TO_24) { - final Sensor parentSensor = parentSensor( - stateStoreLevelGroup, - threadId, - taskId, - storeType, - metricName, - latencyMetricName, - descriptionOfRate, - descriptionOfCount, - descriptionOfAvg, - descriptionOfMax, - recordingLevel, - streamsMetrics - ); - sensor = streamsMetrics.storeLevelSensor(taskId, storeName, metricName, recordingLevel, parentSensor); - addInvocationRateAndCountToSensor( - sensor, - stateStoreLevelGroup, - tagMap, - metricName, - descriptionOfRate, - descriptionOfCount - ); - } else { - sensor = streamsMetrics.storeLevelSensor(taskId, storeName, metricName, recordingLevel); - addInvocationRateToSensor(sensor, stateStoreLevelGroup, tagMap, metricName, descriptionOfRate); - } + sensor = streamsMetrics.storeLevelSensor(taskId, storeName, metricName, recordingLevel); + addInvocationRateToSensor(sensor, STATE_STORE_LEVEL_GROUP, tagMap, metricName, descriptionOfRate); addAvgAndMaxToSensor( sensor, - stateStoreLevelGroup, + STATE_STORE_LEVEL_GROUP, tagMap, latencyMetricName, descriptionOfAvg, @@ -578,45 +478,4 @@ private static Sensor throughputAndLatencySensor(final String threadId, ); return sensor; } - - private static Sensor parentSensor(final String stateStoreLevelGroup, - final String threadId, - final String taskId, - final String storeType, - final String metricName, - final String latencyMetricName, - final String descriptionOfRate, - final String descriptionOfCount, - final String descriptionOfAvg, - final String descriptionOfMax, - final RecordingLevel recordingLevel, - final StreamsMetricsImpl streamsMetrics) { - final Sensor sensor = streamsMetrics.taskLevelSensor(threadId, taskId, metricName, recordingLevel); - final Map allTagMap = streamsMetrics.storeLevelTagMap(taskId, storeType, ROLLUP_VALUE); - addAvgAndMaxToSensor( - sensor, - stateStoreLevelGroup, - allTagMap, - latencyMetricName, - descriptionOfAvg, - descriptionOfMax - ); - addInvocationRateAndCountToSensor( - sensor, - stateStoreLevelGroup, - allTagMap, - metricName, - descriptionOfRate, - descriptionOfCount - ); - return sensor; - } - - private static String stateStoreLevelGroup(final String metricsScope, final Version builtInMetricsVersion) { - if (builtInMetricsVersion == Version.FROM_0100_TO_24) { - return "stream-" + metricsScope + "-state-metrics"; - } - return STATE_STORE_LEVEL_GROUP; - } - } diff --git a/streams/src/test/java/org/apache/kafka/streams/KafkaStreamsTest.java b/streams/src/test/java/org/apache/kafka/streams/KafkaStreamsTest.java index 22e4bfafa5047..8954d5a26a2f3 100644 --- a/streams/src/test/java/org/apache/kafka/streams/KafkaStreamsTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/KafkaStreamsTest.java @@ -47,11 +47,11 @@ import org.apache.kafka.streams.processor.api.ProcessorContext; import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.processor.internals.GlobalStreamThread; -import org.apache.kafka.streams.processor.internals.InternalTopologyBuilder; import org.apache.kafka.streams.processor.internals.ProcessorTopology; import org.apache.kafka.streams.processor.internals.StateDirectory; import org.apache.kafka.streams.processor.internals.StreamThread; import org.apache.kafka.streams.processor.internals.StreamsMetadataState; +import org.apache.kafka.streams.processor.internals.TopologyMetadata; import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl; import org.apache.kafka.streams.processor.internals.testutil.LogCaptureAppender; import org.apache.kafka.streams.state.KeyValueStore; @@ -98,7 +98,6 @@ import static org.apache.kafka.streams.integration.utils.IntegrationTestUtils.safeUniqueTestName; import static org.apache.kafka.streams.integration.utils.IntegrationTestUtils.waitForApplicationState; -import static org.easymock.EasyMock.anyBoolean; import static org.apache.kafka.test.TestUtils.waitForCondition; import static org.easymock.EasyMock.anyInt; import static org.easymock.EasyMock.anyLong; @@ -131,7 +130,7 @@ public class KafkaStreamsTest { private MockTime time; private Properties props; - + @Mock private StateDirectory stateDirectory; @Mock @@ -220,7 +219,7 @@ private void prepareStreams() throws Exception { // setup stream threads PowerMock.mockStatic(StreamThread.class); EasyMock.expect(StreamThread.create( - anyObject(InternalTopologyBuilder.class), + anyObject(TopologyMetadata.class), anyObject(StreamsConfig.class), anyObject(KafkaClientSupplier.class), anyObject(Admin.class), @@ -379,78 +378,84 @@ private void prepareStreamThread(final StreamThread thread, @Test public void testShouldTransitToNotRunningIfCloseRightAfterCreated() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.close(); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.close(); - Assert.assertEquals(KafkaStreams.State.NOT_RUNNING, streams.state()); + Assert.assertEquals(KafkaStreams.State.NOT_RUNNING, streams.state()); + } } @Test public void stateShouldTransitToRunningIfNonDeadThreadsBackToRunning() throws InterruptedException { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.setStateListener(streamsStateListener); - - Assert.assertEquals(0, streamsStateListener.numChanges); - Assert.assertEquals(KafkaStreams.State.CREATED, streams.state()); - - streams.start(); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.setStateListener(streamsStateListener); - waitForCondition( - () -> streamsStateListener.numChanges == 2, - "Streams never started."); - Assert.assertEquals(KafkaStreams.State.RUNNING, streams.state()); + Assert.assertEquals(0, streamsStateListener.numChanges); + Assert.assertEquals(KafkaStreams.State.CREATED, streams.state()); - for (final StreamThread thread: streams.threads) { - threadStatelistenerCapture.getValue().onChange( - thread, - StreamThread.State.PARTITIONS_REVOKED, - StreamThread.State.RUNNING); - } + streams.start(); - Assert.assertEquals(3, streamsStateListener.numChanges); - Assert.assertEquals(KafkaStreams.State.REBALANCING, streams.state()); + waitForCondition( + () -> streamsStateListener.numChanges == 2, + "Streams never started."); + Assert.assertEquals(KafkaStreams.State.RUNNING, streams.state()); + waitForCondition( + () -> streamsStateListener.numChanges == 2, + "Streams never started."); + Assert.assertEquals(KafkaStreams.State.RUNNING, streams.state()); - for (final StreamThread thread : streams.threads) { - threadStatelistenerCapture.getValue().onChange( - thread, - StreamThread.State.PARTITIONS_ASSIGNED, - StreamThread.State.PARTITIONS_REVOKED); - } + for (final StreamThread thread : streams.threads) { + threadStatelistenerCapture.getValue().onChange( + thread, + StreamThread.State.PARTITIONS_REVOKED, + StreamThread.State.RUNNING); + } - Assert.assertEquals(3, streamsStateListener.numChanges); - Assert.assertEquals(KafkaStreams.State.REBALANCING, streams.state()); + Assert.assertEquals(3, streamsStateListener.numChanges); + Assert.assertEquals(KafkaStreams.State.REBALANCING, streams.state()); - threadStatelistenerCapture.getValue().onChange( - streams.threads.get(NUM_THREADS - 1), - StreamThread.State.PENDING_SHUTDOWN, - StreamThread.State.PARTITIONS_ASSIGNED); + for (final StreamThread thread : streams.threads) { + threadStatelistenerCapture.getValue().onChange( + thread, + StreamThread.State.PARTITIONS_ASSIGNED, + StreamThread.State.PARTITIONS_REVOKED); + } - threadStatelistenerCapture.getValue().onChange( - streams.threads.get(NUM_THREADS - 1), - StreamThread.State.DEAD, - StreamThread.State.PENDING_SHUTDOWN); + Assert.assertEquals(3, streamsStateListener.numChanges); + Assert.assertEquals(KafkaStreams.State.REBALANCING, streams.state()); - Assert.assertEquals(3, streamsStateListener.numChanges); - Assert.assertEquals(KafkaStreams.State.REBALANCING, streams.state()); + threadStatelistenerCapture.getValue().onChange( + streams.threads.get(NUM_THREADS - 1), + StreamThread.State.PENDING_SHUTDOWN, + StreamThread.State.PARTITIONS_ASSIGNED); - for (final StreamThread thread : streams.threads) { - if (thread != streams.threads.get(NUM_THREADS - 1)) { - threadStatelistenerCapture.getValue().onChange( - thread, - StreamThread.State.RUNNING, - StreamThread.State.PARTITIONS_ASSIGNED); + threadStatelistenerCapture.getValue().onChange( + streams.threads.get(NUM_THREADS - 1), + StreamThread.State.DEAD, + StreamThread.State.PENDING_SHUTDOWN); + + Assert.assertEquals(3, streamsStateListener.numChanges); + Assert.assertEquals(KafkaStreams.State.REBALANCING, streams.state()); + + for (final StreamThread thread : streams.threads) { + if (thread != streams.threads.get(NUM_THREADS - 1)) { + threadStatelistenerCapture.getValue().onChange( + thread, + StreamThread.State.RUNNING, + StreamThread.State.PARTITIONS_ASSIGNED); + } } - } - Assert.assertEquals(4, streamsStateListener.numChanges); - Assert.assertEquals(KafkaStreams.State.RUNNING, streams.state()); + Assert.assertEquals(4, streamsStateListener.numChanges); + Assert.assertEquals(KafkaStreams.State.RUNNING, streams.state()); - streams.close(); + streams.close(); - waitForCondition( - () -> streamsStateListener.numChanges == 6, - "Streams never closed."); - Assert.assertEquals(KafkaStreams.State.NOT_RUNNING, streams.state()); + waitForCondition( + () -> streamsStateListener.numChanges == 6, + "Streams never closed."); + Assert.assertEquals(KafkaStreams.State.NOT_RUNNING, streams.state()); + } } @Test @@ -458,12 +463,13 @@ public void shouldCleanupResourcesOnCloseWithoutPreviousStart() throws Exception final StreamsBuilder builder = getBuilderWithSource(); builder.globalTable("anyTopic"); - final KafkaStreams streams = new KafkaStreams(builder.build(), props, supplier, time); - streams.close(); + try (final KafkaStreams streams = new KafkaStreams(builder.build(), props, supplier, time)) { + streams.close(); - waitForCondition( - () -> streams.state() == KafkaStreams.State.NOT_RUNNING, - "Streams never stopped."); + waitForCondition( + () -> streams.state() == KafkaStreams.State.NOT_RUNNING, + "Streams never stopped."); + } assertTrue(supplier.consumer.closed()); assertTrue(supplier.restoreConsumer.closed()); @@ -477,9 +483,8 @@ public void testStateThreadClose() throws Exception { // make sure we have the global state thread running too final StreamsBuilder builder = getBuilderWithSource(); builder.globalTable("anyTopic"); - final KafkaStreams streams = new KafkaStreams(builder.build(), props, supplier, time); - try { + try (final KafkaStreams streams = new KafkaStreams(builder.build(), props, supplier, time)) { assertEquals(NUM_THREADS, streams.threads.size()); assertEquals(streams.state(), KafkaStreams.State.CREATED); @@ -499,15 +504,14 @@ public void testStateThreadClose() throws Exception { () -> streams.localThreadsMetadata().stream().allMatch(t -> t.threadState().equals("DEAD")), "Streams never stopped" ); - } finally { streams.close(); - } - waitForCondition( - () -> streams.state() == KafkaStreams.State.NOT_RUNNING, - "Streams never stopped."); + waitForCondition( + () -> streams.state() == KafkaStreams.State.NOT_RUNNING, + "Streams never stopped."); - assertNull(streams.globalStreamThread); + assertNull(streams.globalStreamThread); + } } @Test @@ -515,9 +519,8 @@ public void testStateGlobalThreadClose() throws Exception { // make sure we have the global state thread running too final StreamsBuilder builder = getBuilderWithSource(); builder.globalTable("anyTopic"); - final KafkaStreams streams = new KafkaStreams(builder.build(), props, supplier, time); - try { + try (final KafkaStreams streams = new KafkaStreams(builder.build(), props, supplier, time)) { streams.start(); waitForCondition( () -> streams.state() == KafkaStreams.State.RUNNING, @@ -533,14 +536,13 @@ public void testStateGlobalThreadClose() throws Exception { () -> streams.state() == KafkaStreams.State.PENDING_ERROR, "Thread never stopped." ); - } finally { streams.close(); - } - waitForCondition( - () -> streams.state() == KafkaStreams.State.ERROR, - "Thread never stopped." - ); + waitForCondition( + () -> streams.state() == KafkaStreams.State.ERROR, + "Thread never stopped." + ); + } } @Test @@ -561,141 +563,151 @@ public void testInitializesAndDestroysMetricsReporters() { @Test public void testCloseIsIdempotent() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.close(); - final int closeCount = MockMetricsReporter.CLOSE_COUNT.get(); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.close(); + final int closeCount = MockMetricsReporter.CLOSE_COUNT.get(); - streams.close(); - Assert.assertEquals("subsequent close() calls should do nothing", - closeCount, MockMetricsReporter.CLOSE_COUNT.get()); + streams.close(); + Assert.assertEquals("subsequent close() calls should do nothing", + closeCount, MockMetricsReporter.CLOSE_COUNT.get()); + } } @Test public void shouldAddThreadWhenRunning() throws InterruptedException { props.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG, 1); - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.start(); - final int oldSize = streams.threads.size(); - waitForCondition(() -> streams.state() == KafkaStreams.State.RUNNING, 15L, "wait until running"); - assertThat(streams.addStreamThread(), equalTo(Optional.of("processId-StreamThread-" + 2))); - assertThat(streams.threads.size(), equalTo(oldSize + 1)); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.start(); + final int oldSize = streams.threads.size(); + waitForCondition(() -> streams.state() == KafkaStreams.State.RUNNING, 15L, "wait until running"); + assertThat(streams.addStreamThread(), equalTo(Optional.of("processId-StreamThread-" + 2))); + assertThat(streams.threads.size(), equalTo(oldSize + 1)); + } } @Test public void shouldNotAddThreadWhenCreated() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - final int oldSize = streams.threads.size(); - assertThat(streams.addStreamThread(), equalTo(Optional.empty())); - assertThat(streams.threads.size(), equalTo(oldSize)); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + final int oldSize = streams.threads.size(); + assertThat(streams.addStreamThread(), equalTo(Optional.empty())); + assertThat(streams.threads.size(), equalTo(oldSize)); + } } @Test public void shouldNotAddThreadWhenClosed() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - final int oldSize = streams.threads.size(); - streams.close(); - assertThat(streams.addStreamThread(), equalTo(Optional.empty())); - assertThat(streams.threads.size(), equalTo(oldSize)); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + final int oldSize = streams.threads.size(); + streams.close(); + assertThat(streams.addStreamThread(), equalTo(Optional.empty())); + assertThat(streams.threads.size(), equalTo(oldSize)); + } } @Test public void shouldNotAddThreadWhenError() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - final int oldSize = streams.threads.size(); - streams.start(); - globalStreamThread.shutdown(); - assertThat(streams.addStreamThread(), equalTo(Optional.empty())); - assertThat(streams.threads.size(), equalTo(oldSize)); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + final int oldSize = streams.threads.size(); + streams.start(); + globalStreamThread.shutdown(); + assertThat(streams.addStreamThread(), equalTo(Optional.empty())); + assertThat(streams.threads.size(), equalTo(oldSize)); + } } @Test public void shouldNotReturnDeadThreads() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.start(); - streamThreadOne.shutdown(); - final Set threads = streams.localThreadsMetadata(); - assertThat(threads.size(), equalTo(1)); - assertThat(threads, hasItem(streamThreadTwo.threadMetadata())); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.start(); + streamThreadOne.shutdown(); + final Set threads = streams.localThreadsMetadata(); + assertThat(threads.size(), equalTo(1)); + assertThat(threads, hasItem(streamThreadTwo.threadMetadata())); + } } @Test public void shouldRemoveThread() throws InterruptedException { props.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG, 2); - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.start(); - final int oldSize = streams.threads.size(); - waitForCondition(() -> streams.state() == KafkaStreams.State.RUNNING, 15L, - "Kafka Streams client did not reach state RUNNING"); - assertThat(streams.removeStreamThread(), equalTo(Optional.of("processId-StreamThread-" + 1))); - assertThat(streams.threads.size(), equalTo(oldSize - 1)); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.start(); + final int oldSize = streams.threads.size(); + waitForCondition(() -> streams.state() == KafkaStreams.State.RUNNING, 15L, + "Kafka Streams client did not reach state RUNNING"); + assertThat(streams.removeStreamThread(), equalTo(Optional.of("processId-StreamThread-" + 1))); + assertThat(streams.threads.size(), equalTo(oldSize - 1)); + } } @Test public void shouldNotRemoveThreadWhenNotRunning() { props.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG, 1); - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - assertThat(streams.removeStreamThread(), equalTo(Optional.empty())); - assertThat(streams.threads.size(), equalTo(1)); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + assertThat(streams.removeStreamThread(), equalTo(Optional.empty())); + assertThat(streams.threads.size(), equalTo(1)); + } } @Test public void testCannotStartOnceClosed() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.start(); - streams.close(); - try { + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { streams.start(); - fail("Should have throw IllegalStateException"); - } catch (final IllegalStateException expected) { - // this is ok - } finally { streams.close(); + try { + streams.start(); + fail("Should have throw IllegalStateException"); + } catch (final IllegalStateException expected) { + // this is ok + } } } @Test public void shouldNotSetGlobalRestoreListenerAfterStarting() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.start(); - try { - streams.setGlobalStateRestoreListener(null); - fail("Should throw an IllegalStateException"); - } catch (final IllegalStateException e) { - // expected - } finally { - streams.close(); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.start(); + try { + streams.setGlobalStateRestoreListener(null); + fail("Should throw an IllegalStateException"); + } catch (final IllegalStateException e) { + // expected + } } } @Test public void shouldThrowExceptionSettingUncaughtExceptionHandlerNotInCreateState() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.start(); - assertThrows(IllegalStateException.class, () -> streams.setUncaughtExceptionHandler((StreamsUncaughtExceptionHandler) null)); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.start(); + assertThrows(IllegalStateException.class, () -> streams.setUncaughtExceptionHandler((StreamsUncaughtExceptionHandler) null)); + } } @Test public void shouldThrowExceptionSettingStreamsUncaughtExceptionHandlerNotInCreateState() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.start(); - assertThrows(IllegalStateException.class, () -> streams.setUncaughtExceptionHandler((StreamsUncaughtExceptionHandler) null)); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.start(); + assertThrows(IllegalStateException.class, () -> streams.setUncaughtExceptionHandler((StreamsUncaughtExceptionHandler) null)); + } } @Test public void shouldThrowNullPointerExceptionSettingStreamsUncaughtExceptionHandlerIfNull() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - assertThrows(NullPointerException.class, () -> streams.setUncaughtExceptionHandler((StreamsUncaughtExceptionHandler) null)); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + assertThrows(NullPointerException.class, () -> streams.setUncaughtExceptionHandler((StreamsUncaughtExceptionHandler) null)); + } } @Test public void shouldThrowExceptionSettingStateListenerNotInCreateState() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.start(); - try { - streams.setStateListener(null); - fail("Should throw IllegalStateException"); - } catch (final IllegalStateException e) { - // expected + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.start(); + try { + streams.setStateListener(null); + fail("Should throw IllegalStateException"); + } catch (final IllegalStateException e) { + // expected + } } } @@ -713,17 +725,18 @@ public void shouldAllowCleanupBeforeStartAndAfterClose() { @Test public void shouldThrowOnCleanupWhileRunning() throws InterruptedException { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.start(); - waitForCondition( - () -> streams.state() == KafkaStreams.State.RUNNING, - "Streams never started."); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.start(); + waitForCondition( + () -> streams.state() == KafkaStreams.State.RUNNING, + "Streams never started."); - try { - streams.cleanUp(); - fail("Should have thrown IllegalStateException"); - } catch (final IllegalStateException expected) { - assertEquals("Cannot clean up while running.", expected.getMessage()); + try { + streams.cleanUp(); + fail("Should have thrown IllegalStateException"); + } catch (final IllegalStateException expected) { + assertEquals("Cannot clean up while running.", expected.getMessage()); + } } } @@ -779,9 +792,10 @@ public void shouldNotGetQueryMetadataWithSerializerWhenNotRunningOrRebalancing() @Test public void shouldGetQueryMetadataWithSerializerWhenRunningOrRebalancing() { - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time); - streams.start(); - assertEquals(KeyQueryMetadata.NOT_AVAILABLE, streams.queryMetadataForKey("store", "key", Serdes.String().serializer())); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, supplier, time)) { + streams.start(); + assertEquals(KeyQueryMetadata.NOT_AVAILABLE, streams.queryMetadataForKey("store", "key", Serdes.String().serializer())); + } } @Test @@ -833,9 +847,10 @@ public void shouldReturnEmptyLocalStorePartitionLags() { EasyMock.expect(mockClientSupplier.getAdmin(anyObject())).andReturn(mockAdminClient); EasyMock.replay(result, mockAdminClient, mockClientSupplier); - final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, mockClientSupplier, time); - streams.start(); - assertEquals(0, streams.allLocalStorePartitionLags().size()); + try (final KafkaStreams streams = new KafkaStreams(getBuilderWithSource().build(), props, mockClientSupplier, time)) { + streams.start(); + assertEquals(0, streams.allLocalStorePartitionLags().size()); + } } @Test @@ -951,7 +966,7 @@ public void shouldCleanupOldStateDirs() throws Exception { anyObject(StreamsConfig.class), anyObject(Time.class), EasyMock.eq(true), - anyBoolean() + EasyMock.eq(false) ).andReturn(stateDirectory); EasyMock.expect(stateDirectory.initializeProcessId()).andReturn(UUID.randomUUID()); stateDirectory.close(); @@ -1035,30 +1050,32 @@ public void shouldThrowTopologyExceptionOnEmptyTopology() { public void shouldNotCreateStreamThreadsForGlobalOnlyTopology() { final StreamsBuilder builder = new StreamsBuilder(); builder.globalTable("anyTopic"); - final KafkaStreams streams = new KafkaStreams(builder.build(), props, supplier, time); + try (final KafkaStreams streams = new KafkaStreams(builder.build(), props, supplier, time)) { - assertThat(streams.threads.size(), equalTo(0)); + assertThat(streams.threads.size(), equalTo(0)); + } } @Test public void shouldTransitToRunningWithGlobalOnlyTopology() throws InterruptedException { final StreamsBuilder builder = new StreamsBuilder(); builder.globalTable("anyTopic"); - final KafkaStreams streams = new KafkaStreams(builder.build(), props, supplier, time); + try (final KafkaStreams streams = new KafkaStreams(builder.build(), props, supplier, time)) { - assertThat(streams.threads.size(), equalTo(0)); - assertEquals(streams.state(), KafkaStreams.State.CREATED); + assertThat(streams.threads.size(), equalTo(0)); + assertEquals(streams.state(), KafkaStreams.State.CREATED); - streams.start(); - waitForCondition( - () -> streams.state() == KafkaStreams.State.RUNNING, - "Streams never started, state is " + streams.state()); + streams.start(); + waitForCondition( + () -> streams.state() == KafkaStreams.State.RUNNING, + "Streams never started, state is " + streams.state()); - streams.close(); + streams.close(); - waitForCondition( - () -> streams.state() == KafkaStreams.State.NOT_RUNNING, - "Streams never stopped."); + waitForCondition( + () -> streams.state() == KafkaStreams.State.NOT_RUNNING, + "Streams never stopped."); + } } @SuppressWarnings("unchecked") @@ -1110,7 +1127,7 @@ public void process(final Record record) { new MockProcessorSupplier<>()); return topology; } - + private StreamsBuilder getBuilderWithSource() { final StreamsBuilder builder = new StreamsBuilder(); builder.stream("source-topic"); @@ -1123,7 +1140,7 @@ private void startStreamsAndCheckDirExists(final Topology topology, anyObject(StreamsConfig.class), anyObject(Time.class), EasyMock.eq(shouldFilesExist), - anyBoolean() + EasyMock.eq(false) ).andReturn(stateDirectory); EasyMock.expect(stateDirectory.initializeProcessId()).andReturn(UUID.randomUUID()); diff --git a/streams/src/test/java/org/apache/kafka/streams/StreamsConfigTest.java b/streams/src/test/java/org/apache/kafka/streams/StreamsConfigTest.java index 6397da794d261..a4f051e13b737 100644 --- a/streams/src/test/java/org/apache/kafka/streams/StreamsConfigTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/StreamsConfigTest.java @@ -604,13 +604,6 @@ public void shouldThrowExceptionIfNotAtLeastOnceOrExactlyOnce() { assertThrows(ConfigException.class, () -> new StreamsConfig(props)); } - @Test - public void shouldAcceptBuiltInMetricsVersion0100To24() { - // don't use `StreamsConfig.METRICS_0100_TO_24` to actually do a useful test - props.put(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, "0.10.0-2.4"); - new StreamsConfig(props); - } - @Test public void shouldAcceptBuiltInMetricsLatestVersion() { // don't use `StreamsConfig.METRICS_LATEST` to actually do a useful test diff --git a/streams/src/test/java/org/apache/kafka/streams/integration/InternalTopicIntegrationTest.java b/streams/src/test/java/org/apache/kafka/streams/integration/InternalTopicIntegrationTest.java index eb241c566f648..06bd3ec4ebe48 100644 --- a/streams/src/test/java/org/apache/kafka/streams/integration/InternalTopicIntegrationTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/integration/InternalTopicIntegrationTest.java @@ -207,7 +207,7 @@ public void shouldCompactTopicsForKeyValueStoreChangelogs() { waitForCompletion(streams, 2, 30000L); streams.close(); - final Properties changelogProps = getTopicProperties(ProcessorStateManager.storeChangelogTopic(appID, "Counts")); + final Properties changelogProps = getTopicProperties(ProcessorStateManager.storeChangelogTopic(appID, "Counts", null)); assertEquals(LogConfig.Compact(), changelogProps.getProperty(LogConfig.CleanupPolicyProp())); final Properties repartitionProps = getTopicProperties(appID + "-Counts-repartition"); @@ -246,7 +246,7 @@ public void shouldCompactAndDeleteTopicsForWindowStoreChangelogs() { // waitForCompletion(streams, 2, 30000L); streams.close(); - final Properties properties = getTopicProperties(ProcessorStateManager.storeChangelogTopic(appID, "CountWindows")); + final Properties properties = getTopicProperties(ProcessorStateManager.storeChangelogTopic(appID, "CountWindows", null)); final List policies = Arrays.asList(properties.getProperty(LogConfig.CleanupPolicyProp()).split(",")); assertEquals(2, policies.size()); assertTrue(policies.contains(LogConfig.Compact())); diff --git a/streams/src/test/java/org/apache/kafka/streams/integration/MetricsIntegrationTest.java b/streams/src/test/java/org/apache/kafka/streams/integration/MetricsIntegrationTest.java index a379d9366f0ba..b8ee31bab3b90 100644 --- a/streams/src/test/java/org/apache/kafka/streams/integration/MetricsIntegrationTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/integration/MetricsIntegrationTest.java @@ -87,7 +87,6 @@ public static void closeCluster() { // Metric group private static final String STREAM_CLIENT_NODE_METRICS = "stream-metrics"; - private static final String STREAM_THREAD_NODE_METRICS_0100_TO_24 = "stream-metrics"; private static final String STREAM_THREAD_NODE_METRICS = "stream-thread-metrics"; private static final String STREAM_TASK_NODE_METRICS = "stream-task-metrics"; private static final String STREAM_PROCESSOR_NODE_METRICS = "stream-processor-node-metrics"; @@ -96,13 +95,7 @@ public static void closeCluster() { private static final String IN_MEMORY_KVSTORE_TAG_KEY = "in-memory-state-id"; private static final String IN_MEMORY_LRUCACHE_TAG_KEY = "in-memory-lru-state-id"; private static final String ROCKSDB_KVSTORE_TAG_KEY = "rocksdb-state-id"; - private static final String STATE_STORE_LEVEL_GROUP_IN_MEMORY_KVSTORE_0100_TO_24 = "stream-in-memory-state-metrics"; - private static final String STATE_STORE_LEVEL_GROUP_IN_MEMORY_LRUCACHE_0100_TO_24 = "stream-in-memory-lru-state-metrics"; - private static final String STATE_STORE_LEVEL_GROUP_ROCKSDB_KVSTORE_0100_TO_24 = "stream-rocksdb-state-metrics"; private static final String STATE_STORE_LEVEL_GROUP = "stream-state-metrics"; - private static final String STATE_STORE_LEVEL_GROUP_ROCKSDB_WINDOW_STORE_0100_TO_24 = "stream-rocksdb-window-state-metrics"; - private static final String STATE_STORE_LEVEL_GROUP_ROCKSDB_SESSION_STORE_0100_TO_24 = "stream-rocksdb-session-state-metrics"; - private static final String BUFFER_LEVEL_GROUP_0100_TO_24 = "stream-buffer-metrics"; // Metrics name private static final String VERSION = "version"; @@ -203,9 +196,6 @@ public static void closeCluster() { private static final String SKIPPED_RECORDS_TOTAL = "skipped-records-total"; private static final String RECORD_LATENESS_AVG = "record-lateness-avg"; private static final String RECORD_LATENESS_MAX = "record-lateness-max"; - private static final String HIT_RATIO_AVG_BEFORE_24 = "hitRatio-avg"; - private static final String HIT_RATIO_MIN_BEFORE_24 = "hitRatio-min"; - private static final String HIT_RATIO_MAX_BEFORE_24 = "hitRatio-max"; private static final String HIT_RATIO_AVG = "hit-ratio-avg"; private static final String HIT_RATIO_MIN = "hit-ratio-min"; private static final String HIT_RATIO_MAX = "hit-ratio-max"; @@ -341,18 +331,7 @@ private void closeApplication() throws Exception { } @Test - public void shouldAddMetricsOnAllLevelsWithBuiltInMetricsLatestVersion() throws Exception { - shouldAddMetricsOnAllLevels(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldAddMetricsOnAllLevelsWithBuiltInMetricsVersion0100To24() throws Exception { - shouldAddMetricsOnAllLevels(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldAddMetricsOnAllLevels(final String builtInMetricsVersion) throws Exception { - streamsConfiguration.put(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); - + public void shouldAddMetricsOnAllLevels() throws Exception { builder.stream(STREAM_INPUT, Consumed.with(Serdes.Integer(), Serdes.String())) .to(STREAM_OUTPUT_1, Produced.with(Serdes.Integer(), Serdes.String())); builder.table(STREAM_OUTPUT_1, @@ -371,25 +350,13 @@ private void shouldAddMetricsOnAllLevels(final String builtInMetricsVersion) thr verifyStateMetric(State.RUNNING); checkClientLevelMetrics(); - checkThreadLevelMetrics(builtInMetricsVersion); - checkTaskLevelMetrics(builtInMetricsVersion); - checkProcessorNodeLevelMetrics(builtInMetricsVersion); - checkKeyValueStoreMetrics( - STATE_STORE_LEVEL_GROUP_IN_MEMORY_KVSTORE_0100_TO_24, - IN_MEMORY_KVSTORE_TAG_KEY, - builtInMetricsVersion - ); - checkKeyValueStoreMetrics( - STATE_STORE_LEVEL_GROUP_ROCKSDB_KVSTORE_0100_TO_24, - ROCKSDB_KVSTORE_TAG_KEY, - builtInMetricsVersion - ); - checkKeyValueStoreMetrics( - STATE_STORE_LEVEL_GROUP_IN_MEMORY_LRUCACHE_0100_TO_24, - IN_MEMORY_LRUCACHE_TAG_KEY, - builtInMetricsVersion - ); - checkCacheMetrics(builtInMetricsVersion); + checkThreadLevelMetrics(); + checkTaskLevelMetrics(); + checkProcessorNodeLevelMetrics(); + checkKeyValueStoreMetrics(IN_MEMORY_KVSTORE_TAG_KEY); + checkKeyValueStoreMetrics(ROCKSDB_KVSTORE_TAG_KEY); + checkKeyValueStoreMetrics(IN_MEMORY_LRUCACHE_TAG_KEY); + checkCacheMetrics(); closeApplication(); @@ -397,18 +364,7 @@ private void shouldAddMetricsOnAllLevels(final String builtInMetricsVersion) thr } @Test - public void shouldAddMetricsForWindowStoreAndSuppressionBufferWithBuiltInMetricsLatestVersion() throws Exception { - shouldAddMetricsForWindowStoreAndSuppressionBuffer(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldAddMetricsForWindowStoreAndSuppressionBufferWithBuiltInMetricsVersion0100To24() throws Exception { - shouldAddMetricsForWindowStoreAndSuppressionBuffer(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldAddMetricsForWindowStoreAndSuppressionBuffer(final String builtInMetricsVersion) throws Exception { - streamsConfiguration.put(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); - + public void shouldAddMetricsForWindowStoreAndSuppressionBuffer() throws Exception { final Duration windowSize = Duration.ofMillis(50); builder.stream(STREAM_INPUT, Consumed.with(Serdes.Integer(), Serdes.String())) .groupByKey() @@ -428,7 +384,7 @@ private void shouldAddMetricsForWindowStoreAndSuppressionBuffer(final String bui verifyStateMetric(State.RUNNING); - checkWindowStoreAndSuppressionBufferMetrics(builtInMetricsVersion); + checkWindowStoreAndSuppressionBufferMetrics(); closeApplication(); @@ -436,18 +392,7 @@ private void shouldAddMetricsForWindowStoreAndSuppressionBuffer(final String bui } @Test - public void shouldAddMetricsForSessionStoreWithBuiltInMetricsLatestVersion() throws Exception { - shouldAddMetricsForSessionStore(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldAddMetricsForSessionStoreWithBuiltInMetricsVersion0100To24() throws Exception { - shouldAddMetricsForSessionStore(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldAddMetricsForSessionStore(final String builtInMetricsVersion) throws Exception { - streamsConfiguration.put(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); - + public void shouldAddMetricsForSessionStore() throws Exception { final Duration inactivityGap = Duration.ofMillis(50); builder.stream(STREAM_INPUT, Consumed.with(Serdes.Integer(), Serdes.String())) .groupByKey() @@ -468,7 +413,7 @@ private void shouldAddMetricsForSessionStore(final String builtInMetricsVersion) verifyStateMetric(State.RUNNING); - checkSessionStoreMetrics(builtInMetricsVersion); + checkSessionStoreMetrics(); closeApplication(); @@ -525,11 +470,9 @@ private void checkClientLevelMetrics() { checkMetricByName(listMetricThread, FAILED_STREAM_THREADS, 1); } - private void checkThreadLevelMetrics(final String builtInMetricsVersion) { + private void checkThreadLevelMetrics() { final List listMetricThread = new ArrayList(kafkaStreams.metrics().values()).stream() - .filter(m -> m.metricName().group().equals( - StreamsConfig.METRICS_LATEST.equals(builtInMetricsVersion) ? STREAM_THREAD_NODE_METRICS - : STREAM_THREAD_NODE_METRICS_0100_TO_24)) + .filter(m -> m.metricName().group().equals(STREAM_THREAD_NODE_METRICS)) .collect(Collectors.toList()); checkMetricByName(listMetricThread, COMMIT_LATENCY_AVG, NUM_THREADS); checkMetricByName(listMetricThread, COMMIT_LATENCY_MAX, NUM_THREADS); @@ -559,82 +502,49 @@ private void checkThreadLevelMetrics(final String builtInMetricsVersion) { checkMetricByName(listMetricThread, TASK_CREATED_TOTAL, NUM_THREADS); checkMetricByName(listMetricThread, TASK_CLOSED_RATE, NUM_THREADS); checkMetricByName(listMetricThread, TASK_CLOSED_TOTAL, NUM_THREADS); - checkMetricByName( - listMetricThread, - SKIPPED_RECORDS_RATE, - StreamsConfig.METRICS_LATEST.equals(builtInMetricsVersion) ? 0 : NUM_THREADS - ); - checkMetricByName( - listMetricThread, - SKIPPED_RECORDS_TOTAL, - StreamsConfig.METRICS_LATEST.equals(builtInMetricsVersion) ? 0 : NUM_THREADS - ); } - private void checkTaskLevelMetrics(final String builtInMetricsVersion) { + private void checkTaskLevelMetrics() { final List listMetricTask = new ArrayList(kafkaStreams.metrics().values()).stream() .filter(m -> m.metricName().group().equals(STREAM_TASK_NODE_METRICS)) .collect(Collectors.toList()); - final int numberOfAddedMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 0 : 4; checkMetricByName(listMetricTask, ENFORCED_PROCESSING_RATE, 4); checkMetricByName(listMetricTask, ENFORCED_PROCESSING_TOTAL, 4); checkMetricByName(listMetricTask, RECORD_LATENESS_AVG, 4); checkMetricByName(listMetricTask, RECORD_LATENESS_MAX, 4); checkMetricByName(listMetricTask, ACTIVE_PROCESS_RATIO, 4); checkMetricByName(listMetricTask, ACTIVE_BUFFER_COUNT, 4); - checkMetricByName(listMetricTask, PROCESS_LATENCY_AVG, numberOfAddedMetrics); - checkMetricByName(listMetricTask, PROCESS_LATENCY_MAX, numberOfAddedMetrics); - checkMetricByName(listMetricTask, PUNCTUATE_LATENCY_AVG, numberOfAddedMetrics); - checkMetricByName(listMetricTask, PUNCTUATE_LATENCY_MAX, numberOfAddedMetrics); - checkMetricByName(listMetricTask, PUNCTUATE_RATE, numberOfAddedMetrics); - checkMetricByName(listMetricTask, PUNCTUATE_TOTAL, numberOfAddedMetrics); - checkMetricByName(listMetricTask, PROCESS_RATE, numberOfAddedMetrics); - checkMetricByName(listMetricTask, PROCESS_TOTAL, numberOfAddedMetrics); + checkMetricByName(listMetricTask, PROCESS_LATENCY_AVG, 4); + checkMetricByName(listMetricTask, PROCESS_LATENCY_MAX, 4); + checkMetricByName(listMetricTask, PUNCTUATE_LATENCY_AVG, 4); + checkMetricByName(listMetricTask, PUNCTUATE_LATENCY_MAX, 4); + checkMetricByName(listMetricTask, PUNCTUATE_RATE, 4); + checkMetricByName(listMetricTask, PUNCTUATE_TOTAL, 4); + checkMetricByName(listMetricTask, PROCESS_RATE, 4); + checkMetricByName(listMetricTask, PROCESS_TOTAL, 4); } - private void checkProcessorNodeLevelMetrics(final String builtInMetricsVersion) { + private void checkProcessorNodeLevelMetrics() { final List listMetricProcessor = new ArrayList(kafkaStreams.metrics().values()).stream() .filter(m -> m.metricName().group().equals(STREAM_PROCESSOR_NODE_METRICS)) .collect(Collectors.toList()); - final int numberOfRemovedMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 18 : 0; - final int numberOfModifiedProcessMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 18 : 4; - final int numberOfModifiedForwardMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 8 : 0; final int numberOfSourceNodes = 4; final int numberOfTerminalNodes = 4; - checkMetricByName(listMetricProcessor, PROCESS_LATENCY_AVG, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, PROCESS_LATENCY_MAX, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, PUNCTUATE_LATENCY_AVG, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, PUNCTUATE_LATENCY_MAX, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, CREATE_LATENCY_AVG, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, CREATE_LATENCY_MAX, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, DESTROY_LATENCY_AVG, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, DESTROY_LATENCY_MAX, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, PROCESS_RATE, numberOfModifiedProcessMetrics); - checkMetricByName(listMetricProcessor, PROCESS_TOTAL, numberOfModifiedProcessMetrics); - checkMetricByName(listMetricProcessor, PUNCTUATE_RATE, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, PUNCTUATE_TOTAL, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, CREATE_RATE, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, CREATE_TOTAL, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, DESTROY_RATE, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, DESTROY_TOTAL, numberOfRemovedMetrics); - checkMetricByName(listMetricProcessor, FORWARD_TOTAL, numberOfModifiedForwardMetrics); - checkMetricByName(listMetricProcessor, FORWARD_RATE, numberOfModifiedForwardMetrics); + checkMetricByName(listMetricProcessor, PROCESS_RATE, 4); + checkMetricByName(listMetricProcessor, PROCESS_TOTAL, 4); checkMetricByName(listMetricProcessor, RECORD_E2E_LATENCY_AVG, numberOfSourceNodes + numberOfTerminalNodes); checkMetricByName(listMetricProcessor, RECORD_E2E_LATENCY_MIN, numberOfSourceNodes + numberOfTerminalNodes); checkMetricByName(listMetricProcessor, RECORD_E2E_LATENCY_MAX, numberOfSourceNodes + numberOfTerminalNodes); } - private void checkKeyValueStoreMetrics(final String group0100To24, - final String tagKey, - final String builtInMetricsVersion) { + private void checkKeyValueStoreMetrics(final String tagKey) { final List listMetricStore = new ArrayList(kafkaStreams.metrics().values()).stream() - .filter(m -> m.metricName().tags().containsKey(tagKey) && - (m.metricName().group().equals(group0100To24) || m.metricName().group().equals(STATE_STORE_LEVEL_GROUP)) - ).collect(Collectors.toList()); + .filter(m -> m.metricName().tags().containsKey(tagKey) && m.metricName().group().equals(STATE_STORE_LEVEL_GROUP)) + .collect(Collectors.toList()); - final int expectedNumberOfLatencyMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 2 : 1; - final int expectedNumberOfRateMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 2 : 1; - final int expectedNumberOfTotalMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 2 : 0; + final int expectedNumberOfLatencyMetrics = 1; + final int expectedNumberOfRateMetrics = 1; + final int expectedNumberOfTotalMetrics = 0; checkMetricByName(listMetricStore, PUT_LATENCY_AVG, expectedNumberOfLatencyMetrics); checkMetricByName(listMetricStore, PUT_LATENCY_MAX, expectedNumberOfLatencyMetrics); checkMetricByName(listMetricStore, PUT_IF_ABSENT_LATENCY_AVG, expectedNumberOfLatencyMetrics); @@ -697,40 +607,21 @@ private void checkMetricsDeregistration() { assertThat(listMetricAfterClosingApp.size(), is(0)); } - private void checkCacheMetrics(final String builtInMetricsVersion) { + private void checkCacheMetrics() { final List listMetricCache = new ArrayList(kafkaStreams.metrics().values()).stream() .filter(m -> m.metricName().group().equals(STREAM_CACHE_NODE_METRICS)) .collect(Collectors.toList()); - checkMetricByName( - listMetricCache, - builtInMetricsVersion.equals(StreamsConfig.METRICS_LATEST) ? HIT_RATIO_AVG : HIT_RATIO_AVG_BEFORE_24, - builtInMetricsVersion.equals(StreamsConfig.METRICS_LATEST) ? 3 : 6 /* includes parent sensors */ - ); - checkMetricByName( - listMetricCache, - builtInMetricsVersion.equals(StreamsConfig.METRICS_LATEST) ? HIT_RATIO_MIN : HIT_RATIO_MIN_BEFORE_24, - builtInMetricsVersion.equals(StreamsConfig.METRICS_LATEST) ? 3 : 6 /* includes parent sensors */ - ); - checkMetricByName( - listMetricCache, - builtInMetricsVersion.equals(StreamsConfig.METRICS_LATEST) ? HIT_RATIO_MAX : HIT_RATIO_MAX_BEFORE_24, - builtInMetricsVersion.equals(StreamsConfig.METRICS_LATEST) ? 3 : 6 /* includes parent sensors */ - ); + checkMetricByName(listMetricCache, HIT_RATIO_AVG, 3); + checkMetricByName(listMetricCache, HIT_RATIO_MIN, 3); + checkMetricByName(listMetricCache, HIT_RATIO_MAX, 3); } - private void checkWindowStoreAndSuppressionBufferMetrics(final String builtInMetricsVersion) { + private void checkWindowStoreAndSuppressionBufferMetrics() { final List listMetricStore = new ArrayList(kafkaStreams.metrics().values()).stream() - .filter(m -> m.metricName().group().equals(STATE_STORE_LEVEL_GROUP_ROCKSDB_WINDOW_STORE_0100_TO_24) || - m.metricName().group().equals(BUFFER_LEVEL_GROUP_0100_TO_24) || - m.metricName().group().equals("stream-rocksdb-window-metrics") || - m.metricName().group().equals(STATE_STORE_LEVEL_GROUP) - ).collect(Collectors.toList()); - final int expectedNumberOfLatencyMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 2 : 1; - final int expectedNumberOfRateMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 2 : 1; - final int expectedNumberOfTotalMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 2 : 0; - final int expectedNumberOfRemovedMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 1 : 0; - checkMetricByName(listMetricStore, PUT_LATENCY_AVG, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, PUT_LATENCY_MAX, expectedNumberOfLatencyMetrics); + .filter(m -> m.metricName().group().equals(STATE_STORE_LEVEL_GROUP)) + .collect(Collectors.toList()); + checkMetricByName(listMetricStore, PUT_LATENCY_AVG, 1); + checkMetricByName(listMetricStore, PUT_LATENCY_MAX, 1); checkMetricByName(listMetricStore, PUT_IF_ABSENT_LATENCY_AVG, 0); checkMetricByName(listMetricStore, PUT_IF_ABSENT_LATENCY_MAX, 0); checkMetricByName(listMetricStore, GET_LATENCY_AVG, 0); @@ -745,14 +636,13 @@ private void checkWindowStoreAndSuppressionBufferMetrics(final String builtInMet checkMetricByName(listMetricStore, ALL_LATENCY_MAX, 0); checkMetricByName(listMetricStore, RANGE_LATENCY_AVG, 0); checkMetricByName(listMetricStore, RANGE_LATENCY_MAX, 0); - checkMetricByName(listMetricStore, FLUSH_LATENCY_AVG, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, FLUSH_LATENCY_MAX, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, RESTORE_LATENCY_AVG, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, RESTORE_LATENCY_MAX, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, FETCH_LATENCY_AVG, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, FETCH_LATENCY_MAX, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, PUT_RATE, expectedNumberOfRateMetrics); - checkMetricByName(listMetricStore, PUT_TOTAL, expectedNumberOfTotalMetrics); + checkMetricByName(listMetricStore, FLUSH_LATENCY_AVG, 1); + checkMetricByName(listMetricStore, FLUSH_LATENCY_MAX, 1); + checkMetricByName(listMetricStore, RESTORE_LATENCY_AVG, 1); + checkMetricByName(listMetricStore, RESTORE_LATENCY_MAX, 1); + checkMetricByName(listMetricStore, FETCH_LATENCY_AVG, 1); + checkMetricByName(listMetricStore, FETCH_LATENCY_MAX, 1); + checkMetricByName(listMetricStore, PUT_RATE, 1); checkMetricByName(listMetricStore, PUT_IF_ABSENT_RATE, 0); checkMetricByName(listMetricStore, PUT_IF_ABSENT_TOTAL, 0); checkMetricByName(listMetricStore, GET_RATE, 0); @@ -767,18 +657,11 @@ private void checkWindowStoreAndSuppressionBufferMetrics(final String builtInMet checkMetricByName(listMetricStore, ALL_TOTAL, 0); checkMetricByName(listMetricStore, RANGE_RATE, 0); checkMetricByName(listMetricStore, RANGE_TOTAL, 0); - checkMetricByName(listMetricStore, FLUSH_RATE, expectedNumberOfRateMetrics); - checkMetricByName(listMetricStore, FLUSH_TOTAL, expectedNumberOfTotalMetrics); - checkMetricByName(listMetricStore, RESTORE_RATE, expectedNumberOfRateMetrics); - checkMetricByName(listMetricStore, RESTORE_TOTAL, expectedNumberOfTotalMetrics); - checkMetricByName(listMetricStore, FETCH_RATE, expectedNumberOfRateMetrics); - checkMetricByName(listMetricStore, FETCH_TOTAL, expectedNumberOfTotalMetrics); - checkMetricByName(listMetricStore, EXPIRED_WINDOW_RECORD_DROP_RATE, expectedNumberOfRemovedMetrics); - checkMetricByName(listMetricStore, EXPIRED_WINDOW_RECORD_DROP_TOTAL, expectedNumberOfRemovedMetrics); - checkMetricByName(listMetricStore, SUPPRESSION_BUFFER_COUNT_CURRENT, expectedNumberOfRemovedMetrics); + checkMetricByName(listMetricStore, FLUSH_RATE, 1); + checkMetricByName(listMetricStore, RESTORE_RATE, 1); + checkMetricByName(listMetricStore, FETCH_RATE, 1); checkMetricByName(listMetricStore, SUPPRESSION_BUFFER_COUNT_AVG, 1); checkMetricByName(listMetricStore, SUPPRESSION_BUFFER_COUNT_MAX, 1); - checkMetricByName(listMetricStore, SUPPRESSION_BUFFER_SIZE_CURRENT, expectedNumberOfRemovedMetrics); checkMetricByName(listMetricStore, SUPPRESSION_BUFFER_SIZE_AVG, 1); checkMetricByName(listMetricStore, SUPPRESSION_BUFFER_SIZE_MAX, 1); checkMetricByName(listMetricStore, RECORD_E2E_LATENCY_AVG, 1); @@ -786,63 +669,49 @@ private void checkWindowStoreAndSuppressionBufferMetrics(final String builtInMet checkMetricByName(listMetricStore, RECORD_E2E_LATENCY_MAX, 1); } - private void checkSessionStoreMetrics(final String builtInMetricsVersion) { + private void checkSessionStoreMetrics() { final List listMetricStore = new ArrayList(kafkaStreams.metrics().values()).stream() - .filter(m -> m.metricName().group().equals(STATE_STORE_LEVEL_GROUP_ROCKSDB_SESSION_STORE_0100_TO_24) || - m.metricName().group().equals(BUFFER_LEVEL_GROUP_0100_TO_24) || - m.metricName().group().equals("stream-rocksdb-session-metrics") || - m.metricName().group().equals(STATE_STORE_LEVEL_GROUP) - ).collect(Collectors.toList()); - final int expectedNumberOfLatencyMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 2 : 1; - final int expectedNumberOfRateMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 2 : 1; - final int expectedNumberOfTotalMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 2 : 0; - final int expectedNumberOfRemovedMetrics = StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? 1 : 0; - checkMetricByName(listMetricStore, PUT_LATENCY_AVG, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, PUT_LATENCY_MAX, expectedNumberOfLatencyMetrics); + .filter(m -> m.metricName().group().equals(STATE_STORE_LEVEL_GROUP)) + .collect(Collectors.toList()); + checkMetricByName(listMetricStore, PUT_LATENCY_AVG, 1); + checkMetricByName(listMetricStore, PUT_LATENCY_MAX, 1); checkMetricByName(listMetricStore, PUT_IF_ABSENT_LATENCY_AVG, 0); checkMetricByName(listMetricStore, PUT_IF_ABSENT_LATENCY_MAX, 0); checkMetricByName(listMetricStore, GET_LATENCY_AVG, 0); checkMetricByName(listMetricStore, GET_LATENCY_MAX, 0); checkMetricByName(listMetricStore, DELETE_LATENCY_AVG, 0); checkMetricByName(listMetricStore, DELETE_LATENCY_MAX, 0); - checkMetricByName(listMetricStore, REMOVE_LATENCY_AVG, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, REMOVE_LATENCY_MAX, expectedNumberOfLatencyMetrics); + checkMetricByName(listMetricStore, REMOVE_LATENCY_AVG, 1); + checkMetricByName(listMetricStore, REMOVE_LATENCY_MAX, 1); checkMetricByName(listMetricStore, PUT_ALL_LATENCY_AVG, 0); checkMetricByName(listMetricStore, PUT_ALL_LATENCY_MAX, 0); checkMetricByName(listMetricStore, ALL_LATENCY_AVG, 0); checkMetricByName(listMetricStore, ALL_LATENCY_MAX, 0); checkMetricByName(listMetricStore, RANGE_LATENCY_AVG, 0); checkMetricByName(listMetricStore, RANGE_LATENCY_MAX, 0); - checkMetricByName(listMetricStore, FLUSH_LATENCY_AVG, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, FLUSH_LATENCY_MAX, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, RESTORE_LATENCY_AVG, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, RESTORE_LATENCY_MAX, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, FETCH_LATENCY_AVG, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, FETCH_LATENCY_MAX, expectedNumberOfLatencyMetrics); - checkMetricByName(listMetricStore, PUT_RATE, expectedNumberOfRateMetrics); - checkMetricByName(listMetricStore, PUT_TOTAL, expectedNumberOfTotalMetrics); + checkMetricByName(listMetricStore, FLUSH_LATENCY_AVG, 1); + checkMetricByName(listMetricStore, FLUSH_LATENCY_MAX, 1); + checkMetricByName(listMetricStore, RESTORE_LATENCY_AVG, 1); + checkMetricByName(listMetricStore, RESTORE_LATENCY_MAX, 1); + checkMetricByName(listMetricStore, FETCH_LATENCY_AVG, 1); + checkMetricByName(listMetricStore, FETCH_LATENCY_MAX, 1); + checkMetricByName(listMetricStore, PUT_RATE, 1); checkMetricByName(listMetricStore, PUT_IF_ABSENT_RATE, 0); checkMetricByName(listMetricStore, PUT_IF_ABSENT_TOTAL, 0); checkMetricByName(listMetricStore, GET_RATE, 0); checkMetricByName(listMetricStore, GET_TOTAL, 0); checkMetricByName(listMetricStore, DELETE_RATE, 0); checkMetricByName(listMetricStore, DELETE_TOTAL, 0); - checkMetricByName(listMetricStore, REMOVE_RATE, expectedNumberOfRateMetrics); - checkMetricByName(listMetricStore, REMOVE_TOTAL, expectedNumberOfTotalMetrics); + checkMetricByName(listMetricStore, REMOVE_RATE, 1); checkMetricByName(listMetricStore, PUT_ALL_RATE, 0); checkMetricByName(listMetricStore, PUT_ALL_TOTAL, 0); checkMetricByName(listMetricStore, ALL_RATE, 0); checkMetricByName(listMetricStore, ALL_TOTAL, 0); checkMetricByName(listMetricStore, RANGE_RATE, 0); checkMetricByName(listMetricStore, RANGE_TOTAL, 0); - checkMetricByName(listMetricStore, FLUSH_RATE, expectedNumberOfRateMetrics); - checkMetricByName(listMetricStore, FLUSH_TOTAL, expectedNumberOfTotalMetrics); - checkMetricByName(listMetricStore, RESTORE_RATE, expectedNumberOfRateMetrics); - checkMetricByName(listMetricStore, RESTORE_TOTAL, expectedNumberOfTotalMetrics); - checkMetricByName(listMetricStore, FETCH_RATE, expectedNumberOfRateMetrics); - checkMetricByName(listMetricStore, FETCH_TOTAL, expectedNumberOfTotalMetrics); - checkMetricByName(listMetricStore, EXPIRED_WINDOW_RECORD_DROP_RATE, expectedNumberOfRemovedMetrics); - checkMetricByName(listMetricStore, EXPIRED_WINDOW_RECORD_DROP_TOTAL, expectedNumberOfRemovedMetrics); + checkMetricByName(listMetricStore, FLUSH_RATE, 1); + checkMetricByName(listMetricStore, RESTORE_RATE, 1); + checkMetricByName(listMetricStore, FETCH_RATE, 1); checkMetricByName(listMetricStore, SUPPRESSION_BUFFER_COUNT_CURRENT, 0); checkMetricByName(listMetricStore, SUPPRESSION_BUFFER_COUNT_AVG, 0); checkMetricByName(listMetricStore, SUPPRESSION_BUFFER_COUNT_MAX, 0); diff --git a/streams/src/test/java/org/apache/kafka/streams/integration/NamedTopologyIntegrationTest.java b/streams/src/test/java/org/apache/kafka/streams/integration/NamedTopologyIntegrationTest.java index 5ea3d391f9580..6bf8525b524bc 100644 --- a/streams/src/test/java/org/apache/kafka/streams/integration/NamedTopologyIntegrationTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/integration/NamedTopologyIntegrationTest.java @@ -16,17 +16,240 @@ */ package org.apache.kafka.streams.integration; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.common.serialization.LongDeserializer; +import org.apache.kafka.common.serialization.LongSerializer; +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.apache.kafka.streams.KafkaClientSupplier; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.StreamsConfig; +import org.apache.kafka.streams.integration.utils.EmbeddedKafkaCluster; +import org.apache.kafka.streams.integration.utils.IntegrationTestUtils; +import org.apache.kafka.streams.kstream.Materialized; +import org.apache.kafka.streams.processor.internals.DefaultKafkaClientSupplier; +import org.apache.kafka.streams.processor.internals.namedtopology.KafkaStreamsNamedTopologyWrapper; +import org.apache.kafka.streams.processor.internals.namedtopology.NamedTopology; +import org.apache.kafka.streams.processor.internals.namedtopology.NamedTopologyStreamsBuilder; +import org.apache.kafka.streams.state.Stores; +import org.apache.kafka.test.TestUtils; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Pattern; + +import static org.apache.kafka.streams.integration.utils.IntegrationTestUtils.safeUniqueTestName; +import static org.apache.kafka.streams.integration.utils.IntegrationTestUtils.waitUntilMinKeyValueRecordsReceived; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + public class NamedTopologyIntegrationTest { - //TODO KAFKA-12648 - /** - * Things to test in Pt. 2 - Introduce TopologyMetadata to wrap InternalTopologyBuilders of named topologies: - * 1. Verify changelog & repartition topics decorated with named topology - * 2. Make sure app run and works with - * -multiple subtopologies - * -persistent state - * -multi-partition input & output topics - * -standbys - * -piped input and verified output records - * 3. Is the task assignment balanced? Does KIP-441/warmup replica placement work as intended? - */ + + private static final int NUM_BROKERS = 1; + + public static final EmbeddedKafkaCluster CLUSTER = new EmbeddedKafkaCluster(NUM_BROKERS); + + @BeforeClass + public static void startCluster() throws IOException { + CLUSTER.start(); + } + + @AfterClass + public static void closeCluster() { + CLUSTER.stop(); + } + + @Rule + public final TestName testName = new TestName(); + private String appId; + + private String inputStream1; + private String inputStream2; + private String inputStream3; + private String outputStream1; + private String outputStream2; + private String outputStream3; + private String storeChangelog1; + private String storeChangelog2; + private String storeChangelog3; + + final List> standardInputData = asList(KeyValue.pair("A", 100L), KeyValue.pair("B", 200L), KeyValue.pair("A", 300L), KeyValue.pair("C", 400L)); + final List> standardOutputData = asList(KeyValue.pair("B", 1L), KeyValue.pair("A", 2L), KeyValue.pair("C", 1L)); // output of basic count topology with caching + + final KafkaClientSupplier clientSupplier = new DefaultKafkaClientSupplier(); + final Properties producerConfig = TestUtils.producerConfig(CLUSTER.bootstrapServers(), StringSerializer.class, LongSerializer.class); + final Properties consumerConfig = TestUtils.consumerConfig(CLUSTER.bootstrapServers(), StringDeserializer.class, LongDeserializer.class); + + final NamedTopologyStreamsBuilder builder1 = new NamedTopologyStreamsBuilder("topology-1"); + final NamedTopologyStreamsBuilder builder2 = new NamedTopologyStreamsBuilder("topology-2"); + final NamedTopologyStreamsBuilder builder3 = new NamedTopologyStreamsBuilder("topology-3"); + + Properties props; + KafkaStreamsNamedTopologyWrapper streams; + + private Properties configProps() { + final Properties streamsConfiguration = new Properties(); + streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, appId); + streamsConfiguration.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, CLUSTER.bootstrapServers()); + streamsConfiguration.put(StreamsConfig.STATE_DIR_CONFIG, TestUtils.tempDirectory(appId).getPath()); + streamsConfiguration.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); + streamsConfiguration.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.Long().getClass()); + streamsConfiguration.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG, 2); + streamsConfiguration.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 1000L); + streamsConfiguration.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + return streamsConfiguration; + } + + @Before + public void setup() throws InterruptedException { + appId = safeUniqueTestName(NamedTopologyIntegrationTest.class, testName); + inputStream1 = appId + "-input-stream-1"; + inputStream2 = appId + "-input-stream-2"; + inputStream3 = appId + "-input-stream-3"; + outputStream1 = appId + "-output-stream-1"; + outputStream2 = appId + "-output-stream-2"; + outputStream3 = appId + "-output-stream-3"; + storeChangelog1 = appId + "-topology-1-store-changelog"; + storeChangelog2 = appId + "-topology-2-store-changelog"; + storeChangelog3 = appId + "-topology-3-store-changelog"; + props = configProps(); + CLUSTER.createTopic(inputStream1, 2, 1); + CLUSTER.createTopic(inputStream2, 2, 1); + CLUSTER.createTopic(inputStream3, 2, 1); + CLUSTER.createTopic(outputStream1, 2, 1); + CLUSTER.createTopic(outputStream2, 2, 1); + CLUSTER.createTopic(outputStream3, 2, 1); + } + + @After + public void shutdown() throws Exception { + if (streams != null) { + streams.close(Duration.ofSeconds(30)); + } + CLUSTER.deleteTopics(inputStream1, inputStream2, inputStream3, outputStream1, outputStream2, outputStream3); + } + + @Test + public void shouldProcessSingleNamedTopologyAndPrefixInternalTopics() throws Exception { + produceToInputTopics(inputStream1, standardInputData); + builder1.stream(inputStream1) + .selectKey((k, v) -> k) + .groupByKey() + .count(Materialized.as(Stores.persistentKeyValueStore("store"))) + .toStream().to(outputStream1); + streams = new KafkaStreamsNamedTopologyWrapper(builder1.buildNamedTopology(props), props, clientSupplier); + IntegrationTestUtils.startApplicationAndWaitUntilRunning(singletonList(streams), Duration.ofSeconds(15)); + final List> results = waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream1, 3); + assertThat(results, equalTo(standardOutputData)); + + final Set allTopics = CLUSTER.getAllTopicsInCluster(); + assertThat(allTopics.contains(appId + "-" + "topology-1" + "-store-changelog"), is(true)); + assertThat(allTopics.contains(appId + "-" + "topology-1" + "-store-repartition"), is(true)); + } + + @Test + public void shouldProcessMultipleIdenticalNamedTopologiesWithPersistentStateStores() throws Exception { + produceToInputTopics(inputStream1, standardInputData); + produceToInputTopics(inputStream2, standardInputData); + produceToInputTopics(inputStream3, standardInputData); + + builder1.stream(inputStream1).selectKey((k, v) -> k).groupByKey().count(Materialized.as(Stores.persistentKeyValueStore("store"))).toStream().to(outputStream1); + builder2.stream(inputStream2).selectKey((k, v) -> k).groupByKey().count(Materialized.as(Stores.persistentKeyValueStore("store"))).toStream().to(outputStream2); + builder3.stream(inputStream3).selectKey((k, v) -> k).groupByKey().count(Materialized.as(Stores.persistentKeyValueStore("store"))).toStream().to(outputStream3); + streams = new KafkaStreamsNamedTopologyWrapper(buildNamedTopologies(builder1, builder2, builder3), props, clientSupplier); + IntegrationTestUtils.startApplicationAndWaitUntilRunning(singletonList(streams), Duration.ofSeconds(15)); + + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream1, 3), equalTo(standardOutputData)); + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream2, 3), equalTo(standardOutputData)); + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream3, 3), equalTo(standardOutputData)); + + assertThat(CLUSTER.getAllTopicsInCluster().containsAll(asList(storeChangelog1, storeChangelog2, storeChangelog3)), is(true)); + } + + @Test + public void shouldProcessMultipleIdenticalNamedTopologiesWithInMemoryStateStores() throws Exception { + produceToInputTopics(inputStream1, standardInputData); + produceToInputTopics(inputStream2, standardInputData); + produceToInputTopics(inputStream3, standardInputData); + + builder1.stream(inputStream1).selectKey((k, v) -> k).groupByKey().count(Materialized.as(Stores.inMemoryKeyValueStore("store"))).toStream().to(outputStream1); + builder2.stream(inputStream2).selectKey((k, v) -> k).groupByKey().count(Materialized.as(Stores.inMemoryKeyValueStore("store"))).toStream().to(outputStream2); + builder3.stream(inputStream3).selectKey((k, v) -> k).groupByKey().count(Materialized.as(Stores.inMemoryKeyValueStore("store"))).toStream().to(outputStream3); + streams = new KafkaStreamsNamedTopologyWrapper(buildNamedTopologies(builder1, builder2, builder3), props, clientSupplier); + IntegrationTestUtils.startApplicationAndWaitUntilRunning(singletonList(streams), Duration.ofSeconds(15)); + + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream1, 3), equalTo(standardOutputData)); + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream2, 3), equalTo(standardOutputData)); + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream3, 3), equalTo(standardOutputData)); + + assertThat(CLUSTER.getAllTopicsInCluster().containsAll(asList(storeChangelog1, storeChangelog2, storeChangelog3)), is(true)); + } + + @Test + public void shouldAllowPatternSubscriptionWithMultipleNamedTopologies() throws Exception { + produceToInputTopics(inputStream1, standardInputData); + produceToInputTopics(inputStream2, standardInputData); + produceToInputTopics(inputStream3, standardInputData); + + builder1.stream(Pattern.compile(inputStream1)).selectKey((k, v) -> k).groupByKey().count().toStream().to(outputStream1); + builder2.stream(Pattern.compile(inputStream2)).selectKey((k, v) -> k).groupByKey().count().toStream().to(outputStream2); + builder3.stream(Pattern.compile(inputStream3)).selectKey((k, v) -> k).groupByKey().count().toStream().to(outputStream3); + streams = new KafkaStreamsNamedTopologyWrapper(buildNamedTopologies(builder1, builder2, builder3), props, clientSupplier); + IntegrationTestUtils.startApplicationAndWaitUntilRunning(singletonList(streams), Duration.ofSeconds(15)); + + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream1, 3), equalTo(standardOutputData)); + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream2, 3), equalTo(standardOutputData)); + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream3, 3), equalTo(standardOutputData)); + } + + @Test + public void shouldAllowMixedCollectionAndPatternSubscriptionWithMultipleNamedTopologies() throws Exception { + produceToInputTopics(inputStream1, standardInputData); + produceToInputTopics(inputStream2, standardInputData); + produceToInputTopics(inputStream3, standardInputData); + + builder1.stream(inputStream1).selectKey((k, v) -> k).groupByKey().count().toStream().to(outputStream1); + builder2.stream(Pattern.compile(inputStream2)).selectKey((k, v) -> k).groupByKey().count().toStream().to(outputStream2); + builder3.stream(Pattern.compile(inputStream3)).selectKey((k, v) -> k).groupByKey().count().toStream().to(outputStream3); + streams = new KafkaStreamsNamedTopologyWrapper(buildNamedTopologies(builder1, builder2, builder3), props, clientSupplier); + IntegrationTestUtils.startApplicationAndWaitUntilRunning(singletonList(streams), Duration.ofSeconds(15)); + + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream1, 3), equalTo(standardOutputData)); + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream2, 3), equalTo(standardOutputData)); + assertThat(waitUntilMinKeyValueRecordsReceived(consumerConfig, outputStream3, 3), equalTo(standardOutputData)); + } + + private void produceToInputTopics(final String topic, final Collection> records) { + IntegrationTestUtils.produceKeyValuesSynchronously( + topic, + records, + producerConfig, + CLUSTER.time + ); + } + + private List buildNamedTopologies(final NamedTopologyStreamsBuilder... builders) { + final List topologies = new ArrayList<>(); + for (final NamedTopologyStreamsBuilder builder : builders) { + topologies.add(builder.buildNamedTopology(props)); + } + return topologies; + } } diff --git a/streams/src/test/java/org/apache/kafka/streams/integration/OptimizedKTableIntegrationTest.java b/streams/src/test/java/org/apache/kafka/streams/integration/OptimizedKTableIntegrationTest.java index 7f7eabb1e1d30..44744cd3e2be3 100644 --- a/streams/src/test/java/org/apache/kafka/streams/integration/OptimizedKTableIntegrationTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/integration/OptimizedKTableIntegrationTest.java @@ -147,7 +147,7 @@ public void shouldApplyUpdatesToStandbyStore() throws Exception { } final ReadOnlyKeyValueStore newActiveStore = kafkaStreams1WasFirstActive ? store2 : store1; - TestUtils.retryOnExceptionWithTimeout(100, 60 * 1000, () -> { + TestUtils.retryOnExceptionWithTimeout(60 * 1000, 100, () -> { // Assert that after failover we have recovered to the last store write assertThat(newActiveStore.get(key), is(equalTo(batch1NumMessages - 1))); }); @@ -159,7 +159,7 @@ public void shouldApplyUpdatesToStandbyStore() throws Exception { // Assert that all messages in the second batch were processed in a timely manner assertThat(semaphore.tryAcquire(batch2NumMessages, 60, TimeUnit.SECONDS), is(equalTo(true))); - TestUtils.retryOnExceptionWithTimeout(100, 60 * 1000, () -> { + TestUtils.retryOnExceptionWithTimeout(60 * 1000, 100, () -> { // Assert that the current value in store reflects all messages being processed assertThat(newActiveStore.get(key), is(equalTo(totalNumMessages - 1))); }); diff --git a/streams/src/test/java/org/apache/kafka/streams/integration/RestoreIntegrationTest.java b/streams/src/test/java/org/apache/kafka/streams/integration/RestoreIntegrationTest.java index df0645ed9e2e2..28ff0c9e86847 100644 --- a/streams/src/test/java/org/apache/kafka/streams/integration/RestoreIntegrationTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/integration/RestoreIntegrationTest.java @@ -152,7 +152,7 @@ public void shouldRestoreStateFromSourceTopic() throws Exception { createStateForRestoration(inputStream, 0); setCommittedOffset(inputStream, offsetLimitDelta); - final StateDirectory stateDirectory = new StateDirectory(new StreamsConfig(props), new MockTime(), true); + final StateDirectory stateDirectory = new StateDirectory(new StreamsConfig(props), new MockTime(), true, false); // note here the checkpointed offset is the last processed record's offset, so without control message we should write this offset - 1 new OffsetCheckpoint(new File(stateDirectory.getOrCreateDirectoryForTask(new TaskId(0, 0)), ".checkpoint")) .write(Collections.singletonMap(new TopicPartition(inputStream, 0), (long) offsetCheckpointed - 1)); @@ -218,7 +218,7 @@ public void shouldRestoreStateFromChangelogTopic() throws Exception { createStateForRestoration(changelog, 0); createStateForRestoration(inputStream, 10000); - final StateDirectory stateDirectory = new StateDirectory(new StreamsConfig(props), new MockTime(), true); + final StateDirectory stateDirectory = new StateDirectory(new StreamsConfig(props), new MockTime(), true, false); // note here the checkpointed offset is the last processed record's offset, so without control message we should write this offset - 1 new OffsetCheckpoint(new File(stateDirectory.getOrCreateDirectoryForTask(new TaskId(0, 0)), ".checkpoint")) .write(Collections.singletonMap(new TopicPartition(changelog, 0), (long) offsetCheckpointed - 1)); diff --git a/streams/src/test/java/org/apache/kafka/streams/integration/StandbyTaskEOSIntegrationTest.java b/streams/src/test/java/org/apache/kafka/streams/integration/StandbyTaskEOSIntegrationTest.java index 16cefd4ccaf76..4fbe73438b91c 100644 --- a/streams/src/test/java/org/apache/kafka/streams/integration/StandbyTaskEOSIntegrationTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/integration/StandbyTaskEOSIntegrationTest.java @@ -186,7 +186,7 @@ private KafkaStreams buildStreamWithDirtyStateDir(final String stateDirPath, final Properties props = props(stateDirPath); final StateDirectory stateDirectory = new StateDirectory( - new StreamsConfig(props), new MockTime(), true); + new StreamsConfig(props), new MockTime(), true, false); new OffsetCheckpoint(new File(stateDirectory.getOrCreateDirectoryForTask(taskId), ".checkpoint")) .write(Collections.singletonMap(new TopicPartition("unknown-topic", 0), 5L)); diff --git a/streams/src/test/java/org/apache/kafka/streams/integration/utils/IntegrationTestUtils.java b/streams/src/test/java/org/apache/kafka/streams/integration/utils/IntegrationTestUtils.java index 630ffca765d58..da457cbd21bdb 100644 --- a/streams/src/test/java/org/apache/kafka/streams/integration/utils/IntegrationTestUtils.java +++ b/streams/src/test/java/org/apache/kafka/streams/integration/utils/IntegrationTestUtils.java @@ -49,6 +49,7 @@ import org.apache.kafka.streams.processor.internals.StreamThread; import org.apache.kafka.streams.processor.internals.ThreadStateTransitionValidator; import org.apache.kafka.streams.processor.internals.assignment.AssignorConfiguration.AssignmentListener; +import org.apache.kafka.streams.processor.internals.namedtopology.KafkaStreamsNamedTopologyWrapper; import org.apache.kafka.streams.state.QueryableStoreType; import org.apache.kafka.test.TestCondition; import org.apache.kafka.test.TestUtils; @@ -991,9 +992,15 @@ public static boolean isEmptyConsumerGroup(final Admin adminClient, private static StateListener getStateListener(final KafkaStreams streams) { try { - final Field field = streams.getClass().getDeclaredField("stateListener"); - field.setAccessible(true); - return (StateListener) field.get(streams); + if (streams instanceof KafkaStreamsNamedTopologyWrapper) { + final Field field = streams.getClass().getSuperclass().getDeclaredField("stateListener"); + field.setAccessible(true); + return (StateListener) field.get(streams); + } else { + final Field field = streams.getClass().getDeclaredField("stateListener"); + field.setAccessible(true); + return (StateListener) field.get(streams); + } } catch (final IllegalAccessException | NoSuchFieldException e) { throw new RuntimeException("Failed to get StateListener through reflection", e); } diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/SuppressedTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/SuppressedTest.java index 112b9eb70d231..b799884263555 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/SuppressedTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/SuppressedTest.java @@ -22,6 +22,8 @@ import org.apache.kafka.streams.kstream.internals.suppress.SuppressedInternal; import org.junit.Test; +import java.util.Collections; + import static java.lang.Long.MAX_VALUE; import static java.time.Duration.ofMillis; import static org.apache.kafka.streams.kstream.Suppressed.BufferConfig.maxBytes; @@ -46,13 +48,19 @@ public void bufferBuilderShouldBeConsistent() { assertThat( "keys alone should be set", maxRecords(2L), - is(new EagerBufferConfigImpl(2L, MAX_VALUE)) + is(new EagerBufferConfigImpl(2L, MAX_VALUE, Collections.emptyMap())) ); assertThat( "size alone should be set", maxBytes(2L), - is(new EagerBufferConfigImpl(MAX_VALUE, 2L)) + is(new EagerBufferConfigImpl(MAX_VALUE, 2L, Collections.emptyMap())) + ); + + assertThat( + "config should be set even after max records", + maxRecords(2L).withMaxBytes(4L).withLoggingEnabled(Collections.singletonMap("myConfigKey", "myConfigValue")), + is(new EagerBufferConfigImpl(2L, 4L, Collections.singletonMap("myConfigKey", "myConfigValue"))) ); } @@ -91,7 +99,13 @@ public void intermediateEventsShouldAcceptAnyBufferAndSetBounds() { assertThat( "all constraints should be set", untilTimeLimit(ofMillis(2L), maxRecords(3L).withMaxBytes(2L)), - is(new SuppressedInternal<>(null, ofMillis(2), new EagerBufferConfigImpl(3L, 2L), null, false)) + is(new SuppressedInternal<>(null, ofMillis(2), new EagerBufferConfigImpl(3L, 2L, Collections.emptyMap()), null, false)) + ); + + assertThat( + "config is not lost early emit is set", + untilTimeLimit(ofMillis(2), maxRecords(2L).withLoggingEnabled(Collections.singletonMap("myConfigKey", "myConfigValue")).emitEarlyWhenFull()), + is(new SuppressedInternal<>(null, ofMillis(2), new EagerBufferConfigImpl(2L, MAX_VALUE, Collections.singletonMap("myConfigKey", "myConfigValue")), null, false)) ); } @@ -105,13 +119,13 @@ public void finalEventsShouldAcceptStrictBuffersAndSetBounds() { assertThat( untilWindowCloses(maxRecords(2L).shutDownWhenFull()), - is(new FinalResultsSuppressionBuilder<>(null, new StrictBufferConfigImpl(2L, MAX_VALUE, SHUT_DOWN)) + is(new FinalResultsSuppressionBuilder<>(null, new StrictBufferConfigImpl(2L, MAX_VALUE, SHUT_DOWN, Collections.emptyMap())) ) ); assertThat( untilWindowCloses(maxBytes(2L).shutDownWhenFull()), - is(new FinalResultsSuppressionBuilder<>(null, new StrictBufferConfigImpl(MAX_VALUE, 2L, SHUT_DOWN)) + is(new FinalResultsSuppressionBuilder<>(null, new StrictBufferConfigImpl(MAX_VALUE, 2L, SHUT_DOWN, Collections.emptyMap())) ) ); @@ -122,14 +136,79 @@ public void finalEventsShouldAcceptStrictBuffersAndSetBounds() { assertThat( untilWindowCloses(maxRecords(2L).shutDownWhenFull()).withName("name"), - is(new FinalResultsSuppressionBuilder<>("name", new StrictBufferConfigImpl(2L, MAX_VALUE, SHUT_DOWN)) + is(new FinalResultsSuppressionBuilder<>("name", new StrictBufferConfigImpl(2L, MAX_VALUE, SHUT_DOWN, Collections.emptyMap())) ) ); assertThat( untilWindowCloses(maxBytes(2L).shutDownWhenFull()).withName("name"), - is(new FinalResultsSuppressionBuilder<>("name", new StrictBufferConfigImpl(MAX_VALUE, 2L, SHUT_DOWN)) + is(new FinalResultsSuppressionBuilder<>("name", new StrictBufferConfigImpl(MAX_VALUE, 2L, SHUT_DOWN, Collections.emptyMap())) ) ); + + assertThat( + "config is not lost when shutdown when full is set", + untilWindowCloses(maxBytes(2L).withLoggingEnabled(Collections.singletonMap("myConfigKey", "myConfigValue")).shutDownWhenFull()), + is(new FinalResultsSuppressionBuilder<>(null, new StrictBufferConfigImpl(MAX_VALUE, 2L, SHUT_DOWN, Collections.singletonMap("myConfigKey", "myConfigValue")))) + ); + } + + @Test + public void supportLongChainOfMethods() { + final Suppressed.BufferConfig bufferConfig = unbounded() + .emitEarlyWhenFull() + .withMaxRecords(3L) + .withMaxBytes(4L) + .withMaxRecords(5L) + .withMaxBytes(6L); + + assertThat( + "long chain of eager buffer config sets attributes properly", + bufferConfig, + is(new EagerBufferConfigImpl(5L, 6L, Collections.emptyMap())) + ); + assertThat( + "long chain of strict buffer config sets attributes properly", + bufferConfig.shutDownWhenFull(), + is(new StrictBufferConfigImpl(5L, 6L, SHUT_DOWN, Collections.emptyMap())) + ); + + final Suppressed.BufferConfig bufferConfigWithLogging = unbounded() + .withLoggingEnabled(Collections.singletonMap("myConfigKey", "myConfigValue")) + .emitEarlyWhenFull() + .withMaxRecords(3L) + .withMaxBytes(4L) + .withMaxRecords(5L) + .withMaxBytes(6L); + + assertThat( + "long chain of eager buffer config sets attributes properly with logging enabled", + bufferConfigWithLogging, + is(new EagerBufferConfigImpl(5L, 6L, Collections.singletonMap("myConfigKey", "myConfigValue"))) + ); + assertThat( + "long chain of strict buffer config sets attributes properly with logging enabled", + bufferConfigWithLogging.shutDownWhenFull(), + is(new StrictBufferConfigImpl(5L, 6L, SHUT_DOWN, Collections.singletonMap("myConfigKey", "myConfigValue"))) + ); + + final Suppressed.BufferConfig bufferConfigWithLoggingCalledAtTheEnd = unbounded() + .emitEarlyWhenFull() + .withMaxRecords(3L) + .withMaxBytes(4L) + .withMaxRecords(5L) + .withMaxBytes(6L) + .withLoggingEnabled(Collections.singletonMap("myConfigKey", "myConfigValue")); + + assertThat( + "long chain of eager buffer config sets logging even after other setters", + bufferConfigWithLoggingCalledAtTheEnd, + is(new EagerBufferConfigImpl(5L, 6L, Collections.singletonMap("myConfigKey", "myConfigValue"))) + ); + assertThat( + "long chain of strict buffer config sets logging even after other setters", + bufferConfigWithLoggingCalledAtTheEnd.shutDownWhenFull(), + is(new StrictBufferConfigImpl(5L, 6L, SHUT_DOWN, Collections.singletonMap("myConfigKey", "myConfigValue"))) + ); } } diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/InternalStreamsBuilderTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/InternalStreamsBuilderTest.java index 1fffb500a74c4..3f7911efdaf0b 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/InternalStreamsBuilderTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/InternalStreamsBuilderTest.java @@ -16,6 +16,7 @@ */ package org.apache.kafka.streams.kstream.internals; +import org.apache.kafka.clients.consumer.OffsetResetStrategy; import org.apache.kafka.common.utils.Bytes; import org.apache.kafka.common.utils.Utils; import org.apache.kafka.streams.StreamsConfig; @@ -44,10 +45,10 @@ import static java.util.Arrays.asList; import static org.apache.kafka.streams.Topology.AutoOffsetReset; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -251,8 +252,7 @@ public void shouldAddTopicToEarliestAutoOffsetResetList() { builder.stream(Collections.singleton(topicName), consumed); builder.buildAndOptimizeTopology(); - assertTrue(builder.internalTopologyBuilder.earliestResetTopicsPattern().matcher(topicName).matches()); - assertFalse(builder.internalTopologyBuilder.latestResetTopicsPattern().matcher(topicName).matches()); + assertThat(builder.internalTopologyBuilder.offsetResetStrategy(topicName), equalTo(OffsetResetStrategy.EARLIEST)); } @Test @@ -262,8 +262,7 @@ public void shouldAddTopicToLatestAutoOffsetResetList() { final ConsumedInternal consumed = new ConsumedInternal<>(Consumed.with(AutoOffsetReset.LATEST)); builder.stream(Collections.singleton(topicName), consumed); builder.buildAndOptimizeTopology(); - assertTrue(builder.internalTopologyBuilder.latestResetTopicsPattern().matcher(topicName).matches()); - assertFalse(builder.internalTopologyBuilder.earliestResetTopicsPattern().matcher(topicName).matches()); + assertThat(builder.internalTopologyBuilder.offsetResetStrategy(topicName), equalTo(OffsetResetStrategy.LATEST)); } @Test @@ -271,8 +270,7 @@ public void shouldAddTableToEarliestAutoOffsetResetList() { final String topicName = "topic-1"; builder.table(topicName, new ConsumedInternal<>(Consumed.with(AutoOffsetReset.EARLIEST)), materialized); builder.buildAndOptimizeTopology(); - assertTrue(builder.internalTopologyBuilder.earliestResetTopicsPattern().matcher(topicName).matches()); - assertFalse(builder.internalTopologyBuilder.latestResetTopicsPattern().matcher(topicName).matches()); + assertThat(builder.internalTopologyBuilder.offsetResetStrategy(topicName), equalTo(OffsetResetStrategy.EARLIEST)); } @Test @@ -280,8 +278,7 @@ public void shouldAddTableToLatestAutoOffsetResetList() { final String topicName = "topic-1"; builder.table(topicName, new ConsumedInternal<>(Consumed.with(AutoOffsetReset.LATEST)), materialized); builder.buildAndOptimizeTopology(); - assertTrue(builder.internalTopologyBuilder.latestResetTopicsPattern().matcher(topicName).matches()); - assertFalse(builder.internalTopologyBuilder.earliestResetTopicsPattern().matcher(topicName).matches()); + assertThat(builder.internalTopologyBuilder.offsetResetStrategy(topicName), equalTo(OffsetResetStrategy.LATEST)); } @Test @@ -290,8 +287,7 @@ public void shouldNotAddTableToOffsetResetLists() { builder.table(topicName, consumed, materialized); - assertFalse(builder.internalTopologyBuilder.latestResetTopicsPattern().matcher(topicName).matches()); - assertFalse(builder.internalTopologyBuilder.earliestResetTopicsPattern().matcher(topicName).matches()); + assertThat(builder.internalTopologyBuilder.offsetResetStrategy(topicName), equalTo(OffsetResetStrategy.NONE)); } @Test @@ -301,9 +297,7 @@ public void shouldNotAddRegexTopicsToOffsetResetLists() { builder.stream(topicPattern, consumed); - assertFalse(builder.internalTopologyBuilder.latestResetTopicsPattern().matcher(topic).matches()); - assertFalse(builder.internalTopologyBuilder.earliestResetTopicsPattern().matcher(topic).matches()); - + assertThat(builder.internalTopologyBuilder.offsetResetStrategy(topic), equalTo(OffsetResetStrategy.NONE)); } @Test @@ -314,8 +308,7 @@ public void shouldAddRegexTopicToEarliestAutoOffsetResetList() { builder.stream(topicPattern, new ConsumedInternal<>(Consumed.with(AutoOffsetReset.EARLIEST))); builder.buildAndOptimizeTopology(); - assertTrue(builder.internalTopologyBuilder.earliestResetTopicsPattern().matcher(topicTwo).matches()); - assertFalse(builder.internalTopologyBuilder.latestResetTopicsPattern().matcher(topicTwo).matches()); + assertThat(builder.internalTopologyBuilder.offsetResetStrategy(topicTwo), equalTo(OffsetResetStrategy.EARLIEST)); } @Test @@ -326,8 +319,7 @@ public void shouldAddRegexTopicToLatestAutoOffsetResetList() { builder.stream(topicPattern, new ConsumedInternal<>(Consumed.with(AutoOffsetReset.LATEST))); builder.buildAndOptimizeTopology(); - assertTrue(builder.internalTopologyBuilder.latestResetTopicsPattern().matcher(topicTwo).matches()); - assertFalse(builder.internalTopologyBuilder.earliestResetTopicsPattern().matcher(topicTwo).matches()); + assertThat(builder.internalTopologyBuilder.offsetResetStrategy(topicTwo), equalTo(OffsetResetStrategy.LATEST)); } @Test diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KGroupedStreamImplTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KGroupedStreamImplTest.java index 00925c4f37a58..d3b7626c16894 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KGroupedStreamImplTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KGroupedStreamImplTest.java @@ -16,14 +16,11 @@ */ package org.apache.kafka.streams.kstream.internals; -import org.apache.kafka.common.Metric; -import org.apache.kafka.common.MetricName; import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.common.serialization.StringSerializer; import org.apache.kafka.common.utils.Bytes; import org.apache.kafka.streams.KeyValueTimestamp; import org.apache.kafka.streams.StreamsBuilder; -import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.TestInputTopic; import org.apache.kafka.streams.TopologyTestDriver; import org.apache.kafka.streams.errors.TopologyException; @@ -57,12 +54,10 @@ import java.util.Properties; import static java.time.Duration.ofMillis; -import static org.apache.kafka.test.StreamsTestUtils.getMetricByName; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; @@ -585,35 +580,14 @@ public void shouldCountAndMaterializeResults() { } @Test - public void shouldLogAndMeasureSkipsInAggregateWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeasureSkipsInAggregate(StreamsConfig.METRICS_0100_TO_24); - } - - @Test - public void shouldLogAndMeasureSkipsInAggregateWithBuiltInMetricsVersionLatest() { - shouldLogAndMeasureSkipsInAggregate(StreamsConfig.METRICS_LATEST); - } - - private void shouldLogAndMeasureSkipsInAggregate(final String builtInMetricsVersion) { + public void shouldLogAndMeasureSkipsInAggregate() { groupedStream.count(Materialized.>as("count").withKeySerde(Serdes.String())); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KStreamAggregate.class); final TopologyTestDriver driver = new TopologyTestDriver(builder.build(), props)) { processData(driver); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - final Map metrics = driver.metrics(); - assertEquals( - 1.0, - getMetricByName(metrics, "skipped-records-total", "stream-metrics").metricValue() - ); - assertNotEquals( - 0.0, - getMetricByName(metrics, "skipped-records-rate", "stream-metrics").metricValue() - ); - } assertThat( appender.getMessages(), hasItem("Skipping record due to null key or value. key=[3] value=[null] topic=[topic] partition=[0] " @@ -651,40 +625,19 @@ public void shouldReduceAndMaterializeResults() { } @Test - public void shouldLogAndMeasureSkipsInReduceWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeasureSkipsInReduce(StreamsConfig.METRICS_0100_TO_24); - } - - @Test - public void shouldLogAndMeasureSkipsInReduceWithBuiltInMetricsVersionLatest() { - shouldLogAndMeasureSkipsInReduce(StreamsConfig.METRICS_LATEST); - } - - private void shouldLogAndMeasureSkipsInReduce(final String builtInMetricsVersion) { + public void shouldLogAndMeasureSkipsInReduce() { groupedStream.reduce( MockReducer.STRING_ADDER, Materialized.>as("reduce") .withKeySerde(Serdes.String()) .withValueSerde(Serdes.String()) ); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KStreamReduce.class); final TopologyTestDriver driver = new TopologyTestDriver(builder.build(), props)) { processData(driver); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - final Map metrics = driver.metrics(); - assertEquals( - 1.0, - getMetricByName(metrics, "skipped-records-total", "stream-metrics").metricValue() - ); - assertNotEquals( - 0.0, - getMetricByName(metrics, "skipped-records-rate", "stream-metrics").metricValue() - ); - } assertThat( appender.getMessages(), hasItem("Skipping record due to null key or value. key=[3] value=[null] topic=[topic] partition=[0] " diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamImplTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamImplTest.java index ac5db68b5403b..7bdcea86a4cf5 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamImplTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamImplTest.java @@ -1525,9 +1525,9 @@ public void shouldUseRecordMetadataTimestampExtractorWhenInternalRepartitioningT final ProcessorTopology topology = TopologyWrapper.getInternalTopologyBuilder(builder.build()).setApplicationId("X").buildTopology(); - final SourceNode originalSourceNode = topology.source("topic-1"); + final SourceNode originalSourceNode = topology.source("topic-1"); - for (final SourceNode sourceNode : topology.sources()) { + for (final SourceNode sourceNode : topology.sources()) { if (sourceNode.name().equals(originalSourceNode.name())) { assertNull(sourceNode.getTimestampExtractor()); } else { @@ -1554,9 +1554,9 @@ public void shouldUseRecordMetadataTimestampExtractorWhenInternalRepartitioningT final ProcessorTopology topology = TopologyWrapper.getInternalTopologyBuilder(builder.build()).setApplicationId("X").buildTopology(); - final SourceNode originalSourceNode = topology.source("topic-1"); + final SourceNode originalSourceNode = topology.source("topic-1"); - for (final SourceNode sourceNode : topology.sources()) { + for (final SourceNode sourceNode : topology.sources()) { if (sourceNode.name().equals(originalSourceNode.name())) { assertNull(sourceNode.getTimestampExtractor()); } else { diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamKStreamJoinTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamKStreamJoinTest.java index c3e32447b7f61..00e5b57368a32 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamKStreamJoinTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamKStreamJoinTest.java @@ -55,7 +55,6 @@ import static java.time.Duration.ofMillis; import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.SUBTOPOLOGY_0; -import static org.apache.kafka.test.StreamsTestUtils.getMetricByName; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; @@ -75,14 +74,35 @@ public class KStreamKStreamJoinTest { private final StreamJoined streamJoined = StreamJoined.with(Serdes.String(), Serdes.Integer(), Serdes.Integer()); private final String errorMessagePrefix = "Window settings mismatch. WindowBytesStoreSupplier settings"; - @Test - public void shouldLogAndMeterOnSkippedRecordsWithNullValueWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterOnSkippedRecordsWithNullValue(StreamsConfig.METRICS_0100_TO_24); - } - @Test public void shouldLogAndMeterOnSkippedRecordsWithNullValueWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterOnSkippedRecordsWithNullValue(StreamsConfig.METRICS_LATEST); + final StreamsBuilder builder = new StreamsBuilder(); + + final KStream left = builder.stream("left", Consumed.with(Serdes.String(), Serdes.Integer())); + final KStream right = builder.stream("right", Consumed.with(Serdes.String(), Serdes.Integer())); + + left.join( + right, + Integer::sum, + JoinWindows.of(ofMillis(100)), + StreamJoined.with(Serdes.String(), Serdes.Integer(), Serdes.Integer()) + ); + + props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, StreamsConfig.METRICS_LATEST); + + try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KStreamKStreamJoin.class); + final TopologyTestDriver driver = new TopologyTestDriver(builder.build(), props)) { + + final TestInputTopic inputTopic = + driver.createInputTopic("left", new StringSerializer(), new IntegerSerializer()); + inputTopic.pipeInput("A", null); + + assertThat( + appender.getMessages(), + hasItem("Skipping record due to null key or value. key=[A] value=[null] topic=[left] partition=[0] " + + "offset=[0]") + ); + } } @@ -117,47 +137,6 @@ public void shouldCreateRepartitionTopicsWithUserProvidedName() { assertEquals(expectedTopologyWithUserNamedRepartitionTopics, topology.describe().toString()); } - private void shouldLogAndMeterOnSkippedRecordsWithNullValue(final String builtInMetricsVersion) { - final StreamsBuilder builder = new StreamsBuilder(); - - final KStream left = builder.stream("left", Consumed.with(Serdes.String(), Serdes.Integer())); - final KStream right = builder.stream("right", Consumed.with(Serdes.String(), Serdes.Integer())); - - left.join( - right, - Integer::sum, - JoinWindows.of(ofMillis(100)), - StreamJoined.with(Serdes.String(), Serdes.Integer(), Serdes.Integer()) - ); - - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); - - try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KStreamKStreamJoin.class); - final TopologyTestDriver driver = new TopologyTestDriver(builder.build(), props)) { - - final TestInputTopic inputTopic = - driver.createInputTopic("left", new StringSerializer(), new IntegerSerializer()); - inputTopic.pipeInput("A", null); - - assertThat( - appender.getMessages(), - hasItem("Skipping record due to null key or value. key=[A] value=[null] topic=[left] partition=[0] " - + "offset=[0]") - ); - - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals( - 1.0, - getMetricByName( - driver.metrics(), - "skipped-records-total", - "stream-metrics" - ).metricValue() - ); - } - } - } - @Test public void shouldDisableLoggingOnStreamJoined() { diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamKTableJoinTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamKTableJoinTest.java index f6a191a3ad301..c91455d3bda7d 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamKTableJoinTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamKTableJoinTest.java @@ -16,7 +16,6 @@ */ package org.apache.kafka.streams.kstream.internals; -import static org.apache.kafka.test.StreamsTestUtils.getMetricByName; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -65,7 +64,6 @@ public class KStreamKTableJoinTest { private MockProcessor processor; private TopologyTestDriver driver; private StreamsBuilder builder; - private final Properties props = StreamsTestUtils.getStreamsConfig(Serdes.Integer(), Serdes.String()); private final MockProcessorSupplier supplier = new MockProcessorSupplier<>(); @Before @@ -229,31 +227,12 @@ public void shouldClearTableEntryOnNullValueUpdates() { } @Test - public void shouldLogAndMeterWhenSkippingNullLeftKeyWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterWhenSkippingNullLeftKey(StreamsConfig.METRICS_0100_TO_24); - } - - @Test - public void shouldLogAndMeterWhenSkippingNullLeftKeyWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterWhenSkippingNullLeftKey(StreamsConfig.METRICS_LATEST); - } - - private void shouldLogAndMeterWhenSkippingNullLeftKey(final String builtInMetricsVersion) { - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); - + public void shouldLogAndMeterWhenSkippingNullLeftKey() { try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KStreamKTableJoin.class)) { - driver.close(); - driver = new TopologyTestDriver(builder.build(), props); final TestInputTopic inputTopic = driver.createInputTopic(streamTopic, new IntegerSerializer(), new StringSerializer()); inputTopic.pipeInput(null, "A"); - if (builtInMetricsVersion.equals(StreamsConfig.METRICS_0100_TO_24)) { - assertEquals( - 1.0, - getMetricByName(driver.metrics(), "skipped-records-total", "stream-metrics").metricValue() - ); - } assertThat( appender.getMessages(), hasItem("Skipping record due to null join key or value. key=[null] value=[A] topic=[streamTopic] partition=[0] " @@ -262,23 +241,10 @@ private void shouldLogAndMeterWhenSkippingNullLeftKey(final String builtInMetric } @Test - public void shouldLogAndMeterWhenSkippingNullLeftValueWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterWhenSkippingNullLeftValue(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeterWhenSkippingNullLeftValueWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterWhenSkippingNullLeftValue(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeterWhenSkippingNullLeftValue(final String builtInMetricsVersion) { - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, StreamsConfig.METRICS_0100_TO_24); - driver.close(); - driver = new TopologyTestDriver(builder.build(), props); - final TestInputTopic inputTopic = - driver.createInputTopic(streamTopic, new IntegerSerializer(), new StringSerializer()); - + public void shouldLogAndMeterWhenSkippingNullLeftValue() { try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KStreamKTableJoin.class)) { + final TestInputTopic inputTopic = + driver.createInputTopic(streamTopic, new IntegerSerializer(), new StringSerializer()); inputTopic.pipeInput(1, null); assertThat( @@ -287,13 +253,6 @@ private void shouldLogAndMeterWhenSkippingNullLeftValue(final String builtInMetr + "offset=[0]") ); } - - if (builtInMetricsVersion.equals(StreamsConfig.METRICS_0100_TO_24)) { - assertEquals( - 1.0, - getMetricByName(driver.metrics(), "skipped-records-total", "stream-metrics").metricValue() - ); - } } diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamSessionWindowAggregateProcessorTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamSessionWindowAggregateProcessorTest.java index 09823376cf052..b2d09770d6cdc 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamSessionWindowAggregateProcessorTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamSessionWindowAggregateProcessorTest.java @@ -93,12 +93,13 @@ public class KStreamSessionWindowAggregateProcessorTest { @Before public void setup() { - setup(StreamsConfig.METRICS_LATEST, true); + setup(true); } - private void setup(final String builtInMetricsVersion, final boolean enableCache) { - final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, "test", builtInMetricsVersion, new MockTime()); - context = new InternalMockProcessorContext( + private void setup(final boolean enableCache) { + final StreamsMetricsImpl streamsMetrics = + new StreamsMetricsImpl(metrics, "test", StreamsConfig.METRICS_LATEST, new MockTime()); + context = new InternalMockProcessorContext( TestUtils.tempDirectory(), Serdes.String(), Serdes.String(), @@ -115,7 +116,7 @@ public void forward(final Object key, final Object value, final To to) { results.add(new KeyValueTimestamp<>((Windowed) key, (Change) value, toInternal.timestamp())); } }; - TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor(threadId, context.taskId().toString(), streamsMetrics); + TaskMetrics.droppedRecordsSensor(threadId, context.taskId().toString(), streamsMetrics); initStore(enableCache); processor.init(context); @@ -376,17 +377,8 @@ public void shouldImmediatelyForwardRemovedSessionsWhenMerging() { } @Test - public void shouldLogAndMeterWhenSkippingNullKeyWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterWhenSkippingNullKeyWithBuiltInMetrics(StreamsConfig.METRICS_0100_TO_24); - } - - @Test - public void shouldLogAndMeterWhenSkippingNullKeyWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterWhenSkippingNullKeyWithBuiltInMetrics(StreamsConfig.METRICS_LATEST); - } - - private void shouldLogAndMeterWhenSkippingNullKeyWithBuiltInMetrics(final String builtInMetricsVersion) { - setup(builtInMetricsVersion, false); + public void shouldLogAndMeterWhenSkippingNullKeyWithBuiltInMetrics() { + setup(false); context.setRecordContext( new ProcessorRecordContext(-1, -2, -3, "topic", null) ); @@ -402,31 +394,15 @@ private void shouldLogAndMeterWhenSkippingNullKeyWithBuiltInMetrics(final String ); } - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals( - 1.0, - getMetricByName(context.metrics().metrics(), "skipped-records-total", "stream-metrics").metricValue() - ); - } else { - assertEquals( - 1.0, - getMetricByName(context.metrics().metrics(), "dropped-records-total", "stream-task-metrics").metricValue() - ); - } - } - - @Test - public void shouldLogAndMeterWhenSkippingLateRecordWithZeroGraceWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterWhenSkippingLateRecordWithZeroGrace(StreamsConfig.METRICS_LATEST); + assertEquals( + 1.0, + getMetricByName(context.metrics().metrics(), "dropped-records-total", "stream-task-metrics").metricValue() + ); } @Test - public void shouldLogAndMeterWhenSkippingLateRecordWithZeroGraceWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterWhenSkippingLateRecordWithZeroGrace(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeterWhenSkippingLateRecordWithZeroGrace(final String builtInMetricsVersion) { - setup(builtInMetricsVersion, false); + public void shouldLogAndMeterWhenSkippingLateRecordWithZeroGrace() { + setup(false); final Processor processor = new KStreamSessionWindowAggregate<>( SessionWindows.with(ofMillis(10L)).grace(ofMillis(0L)), STORE_NAME, @@ -464,47 +440,24 @@ private void shouldLogAndMeterWhenSkippingLateRecordWithZeroGrace(final String b final MetricName dropTotal; final MetricName dropRate; - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - dropTotal = new MetricName( - "late-record-drop-total", - "stream-processor-node-metrics", - "The total number of late records dropped", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry("processor-node-id", "TESTING_NODE") - ) - ); - dropRate = new MetricName( - "late-record-drop-rate", - "stream-processor-node-metrics", - "The average number of late records dropped per second", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry("processor-node-id", "TESTING_NODE") - ) - ); - } else { - dropTotal = new MetricName( - "dropped-records-total", - "stream-task-metrics", - "The total number of dropped records", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - ); - dropRate = new MetricName( - "dropped-records-rate", - "stream-task-metrics", - "The average number of dropped records per second", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - ); - } + dropTotal = new MetricName( + "dropped-records-total", + "stream-task-metrics", + "The total number of dropped records", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + ); + dropRate = new MetricName( + "dropped-records-rate", + "stream-task-metrics", + "The average number of dropped records per second", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + ); assertThat(metrics.metrics().get(dropTotal).metricValue(), is(1.0)); assertThat( (Double) metrics.metrics().get(dropRate).metricValue(), @@ -513,17 +466,8 @@ private void shouldLogAndMeterWhenSkippingLateRecordWithZeroGrace(final String b } @Test - public void shouldLogAndMeterWhenSkippingLateRecordWithNonzeroGraceWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterWhenSkippingLateRecordWithNonzeroGrace(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeterWhenSkippingLateRecordWithNonzeroGraceWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterWhenSkippingLateRecordWithNonzeroGrace(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeterWhenSkippingLateRecordWithNonzeroGrace(final String builtInMetricsVersion) { - setup(builtInMetricsVersion, false); + public void shouldLogAndMeterWhenSkippingLateRecordWithNonzeroGrace() { + setup(false); final Processor processor = new KStreamSessionWindowAggregate<>( SessionWindows.with(ofMillis(10L)).grace(ofMillis(1L)), STORE_NAME, @@ -569,47 +513,24 @@ private void shouldLogAndMeterWhenSkippingLateRecordWithNonzeroGrace(final Strin final MetricName dropTotal; final MetricName dropRate; - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - dropTotal = new MetricName( - "late-record-drop-total", - "stream-processor-node-metrics", - "The total number of late records dropped", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry("processor-node-id", "TESTING_NODE") - ) - ); - dropRate = new MetricName( - "late-record-drop-rate", - "stream-processor-node-metrics", - "The average number of late records dropped per second", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry("processor-node-id", "TESTING_NODE") - ) - ); - } else { - dropTotal = new MetricName( - "dropped-records-total", - "stream-task-metrics", - "The total number of dropped records", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - ); - dropRate = new MetricName( - "dropped-records-rate", - "stream-task-metrics", - "The average number of dropped records per second", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - ); - } + dropTotal = new MetricName( + "dropped-records-total", + "stream-task-metrics", + "The total number of dropped records", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + ); + dropRate = new MetricName( + "dropped-records-rate", + "stream-task-metrics", + "The average number of dropped records per second", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + ); assertThat(metrics.metrics().get(dropTotal).metricValue(), is(1.0)); assertThat( diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamWindowAggregateTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamWindowAggregateTest.java index 4270f1afd1c11..88f3091f8a95e 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamWindowAggregateTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KStreamWindowAggregateTest.java @@ -25,7 +25,6 @@ import org.apache.kafka.streams.KeyValueTimestamp; import org.apache.kafka.streams.StreamsBuilder; import org.apache.kafka.streams.TestOutputTopic; -import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.TopologyTestDriver; import org.apache.kafka.streams.kstream.Consumed; import org.apache.kafka.streams.kstream.Grouped; @@ -54,7 +53,6 @@ import static java.util.Arrays.asList; import static org.apache.kafka.common.utils.Utils.mkEntry; import static org.apache.kafka.common.utils.Utils.mkMap; -import static org.apache.kafka.test.StreamsTestUtils.getMetricByName; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.hasItems; @@ -266,16 +264,7 @@ public void testJoin() { } @Test - public void shouldLogAndMeterWhenSkippingNullKeyWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterWhenSkippingNullKey(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeterWhenSkippingNullKeyWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterWhenSkippingNullKey(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeterWhenSkippingNullKey(final String builtInMetricsVersion) { + public void shouldLogAndMeterWhenSkippingNullKey() { final StreamsBuilder builder = new StreamsBuilder(); final String topic = "topic"; @@ -289,8 +278,6 @@ private void shouldLogAndMeterWhenSkippingNullKey(final String builtInMetricsVer Materialized.>as("topic1-Canonicalized").withValueSerde(Serdes.String()) ); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); - try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KStreamWindowAggregate.class); final TopologyTestDriver driver = new TopologyTestDriver(builder.build(), props)) { @@ -298,27 +285,12 @@ private void shouldLogAndMeterWhenSkippingNullKey(final String builtInMetricsVer driver.createInputTopic(topic, new StringSerializer(), new StringSerializer()); inputTopic.pipeInput(null, "1"); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals( - 1.0, - getMetricByName(driver.metrics(), "skipped-records-total", "stream-metrics").metricValue() - ); - } assertThat(appender.getMessages(), hasItem("Skipping record due to null key. value=[1] topic=[topic] partition=[0] offset=[0]")); } } @Test - public void shouldLogAndMeterWhenSkippingExpiredWindowWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterWhenSkippingExpiredWindow(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeterWhenSkippingExpiredWindowWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterWhenSkippingExpiredWindow(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeterWhenSkippingExpiredWindow(final String builtInMetricsVersion) { + public void shouldLogAndMeterWhenSkippingExpiredWindow() { final StreamsBuilder builder = new StreamsBuilder(); final String topic = "topic"; @@ -338,8 +310,6 @@ private void shouldLogAndMeterWhenSkippingExpiredWindow(final String builtInMetr .map((key, value) -> new KeyValue<>(key.toString(), value)) .to("output"); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); - try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KStreamWindowAggregate.class); final TopologyTestDriver driver = new TopologyTestDriver(builder.build(), props)) { @@ -356,7 +326,6 @@ private void shouldLogAndMeterWhenSkippingExpiredWindow(final String builtInMetr assertLatenessMetrics( driver, - builtInMetricsVersion, is(7.0), // how many events get dropped is(100.0), // k:0 is 100ms late, since its time is 0, but it arrives at stream time 100. is(84.875) // (0 + 100 + 99 + 98 + 97 + 96 + 95 + 94) / 8 @@ -384,16 +353,7 @@ private void shouldLogAndMeterWhenSkippingExpiredWindow(final String builtInMetr } @Test - public void shouldLogAndMeterWhenSkippingExpiredWindowByGraceWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterWhenSkippingExpiredWindowByGrace(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeterWhenSkippingExpiredWindowByGraceWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterWhenSkippingExpiredWindowByGrace(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeterWhenSkippingExpiredWindowByGrace(final String builtInMetricsVersion) { + public void shouldLogAndMeterWhenSkippingExpiredWindowByGrace() { final StreamsBuilder builder = new StreamsBuilder(); final String topic = "topic"; @@ -409,8 +369,6 @@ private void shouldLogAndMeterWhenSkippingExpiredWindowByGrace(final String buil .map((key, value) -> new KeyValue<>(key.toString(), value)) .to("output"); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); - try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KStreamWindowAggregate.class); final TopologyTestDriver driver = new TopologyTestDriver(builder.build(), props)) { @@ -425,7 +383,7 @@ private void shouldLogAndMeterWhenSkippingExpiredWindowByGrace(final String buil inputTopic.pipeInput("k", "5", 105L); inputTopic.pipeInput("k", "6", 6L); - assertLatenessMetrics(driver, builtInMetricsVersion, is(7.0), is(194.0), is(97.375)); + assertLatenessMetrics(driver, is(7.0), is(194.0), is(97.375)); assertThat(appender.getMessages(), hasItems( "Skipping record for expired window. key=[k] topic=[topic] partition=[0] offset=[1] timestamp=[100] window=[100,110) expiration=[110] streamTime=[200]", @@ -445,7 +403,6 @@ private void shouldLogAndMeterWhenSkippingExpiredWindowByGrace(final String buil } private void assertLatenessMetrics(final TopologyTestDriver driver, - final String builtInMetricsVersion, final Matcher dropTotal, final Matcher maxLateness, final Matcher avgLateness) { @@ -454,88 +411,45 @@ private void assertLatenessMetrics(final TopologyTestDriver driver, final MetricName dropRateMetric; final MetricName latenessMaxMetric; final MetricName latenessAvgMetric; - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - dropTotalMetric = new MetricName( - "late-record-drop-total", - "stream-processor-node-metrics", - "The total number of dropped late records", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry("processor-node-id", "KSTREAM-AGGREGATE-0000000001") - ) - ); - dropRateMetric = new MetricName( - "late-record-drop-rate", - "stream-processor-node-metrics", - "The average number of dropped late records per second", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry("processor-node-id", "KSTREAM-AGGREGATE-0000000001") - ) - ); - latenessMaxMetric = new MetricName( - "record-lateness-max", - "stream-task-metrics", - "The observed maximum lateness of records in milliseconds, measured by comparing the record " - + "timestamp with the current stream time", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0") - ) - ); - latenessAvgMetric = new MetricName( - "record-lateness-avg", - "stream-task-metrics", - "The observed average lateness of records in milliseconds, measured by comparing the record " - + "timestamp with the current stream time", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0") - ) - ); - } else { - dropTotalMetric = new MetricName( - "dropped-records-total", - "stream-task-metrics", - "The total number of dropped records", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - ); - dropRateMetric = new MetricName( - "dropped-records-rate", - "stream-task-metrics", - "The average number of dropped records per second", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - ); - latenessMaxMetric = new MetricName( - "record-lateness-max", - "stream-task-metrics", - "The observed maximum lateness of records in milliseconds, measured by comparing the record " - + "timestamp with the current stream time", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - ); - latenessAvgMetric = new MetricName( - "record-lateness-avg", - "stream-task-metrics", - "The observed average lateness of records in milliseconds, measured by comparing the record " - + "timestamp with the current stream time", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - ); + dropTotalMetric = new MetricName( + "dropped-records-total", + "stream-task-metrics", + "The total number of dropped records", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + ); + dropRateMetric = new MetricName( + "dropped-records-rate", + "stream-task-metrics", + "The average number of dropped records per second", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + ); + latenessMaxMetric = new MetricName( + "record-lateness-max", + "stream-task-metrics", + "The observed maximum lateness of records in milliseconds, measured by comparing the record " + + "timestamp with the current stream time", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + ); + latenessAvgMetric = new MetricName( + "record-lateness-avg", + "stream-task-metrics", + "The observed average lateness of records in milliseconds, measured by comparing the record " + + "timestamp with the current stream time", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + ); - } assertThat(driver.metrics().get(dropTotalMetric).metricValue(), dropTotal); assertThat(driver.metrics().get(dropRateMetric).metricValue(), not(0.0)); assertThat(driver.metrics().get(latenessMaxMetric).metricValue(), maxLateness); diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableInnerJoinTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableInnerJoinTest.java index f003b52164521..c7de7f70fcc05 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableInnerJoinTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableInnerJoinTest.java @@ -20,7 +20,6 @@ import org.apache.kafka.common.utils.Bytes; import org.apache.kafka.streams.KeyValueTimestamp; import org.apache.kafka.streams.StreamsBuilder; -import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.TestInputTopic; import org.apache.kafka.streams.TestOutputTopic; import org.apache.kafka.streams.TopologyTestDriver; @@ -47,7 +46,6 @@ import java.util.Properties; import java.util.Set; -import static org.apache.kafka.test.StreamsTestUtils.getMetricByName; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; @@ -248,16 +246,7 @@ public void testSendingOldValues() { } @Test - public void shouldLogAndMeterSkippedRecordsDueToNullLeftKeyWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterSkippedRecordsDueToNullLeftKey(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeterSkippedRecordsDueToNullLeftKeyWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterSkippedRecordsDueToNullLeftKey(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtInMetricsVersion) { + public void shouldLogAndMeterSkippedRecordsDueToNullLeftKey() { final StreamsBuilder builder = new StreamsBuilder(); @SuppressWarnings("unchecked") @@ -267,7 +256,6 @@ private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtI null ).get(); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); final MockProcessorContext context = new MockProcessorContext(props); context.setRecordMetadata("left", -1, -2, null, -3); join.init(context); @@ -280,13 +268,6 @@ private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtI hasItem("Skipping record due to null key. change=[(new<-old)] topic=[left] partition=[-1] offset=[-2]") ); } - - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals( - 1.0, - getMetricByName(context.metrics().metrics(), "skipped-records-total", "stream-metrics").metricValue() - ); - } } private void doTestNotSendingOldValues(final StreamsBuilder builder, diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableLeftJoinTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableLeftJoinTest.java index c1bc7fe7df005..451725f00c6b4 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableLeftJoinTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableLeftJoinTest.java @@ -20,7 +20,6 @@ import org.apache.kafka.streams.KeyValue; import org.apache.kafka.streams.KeyValueTimestamp; import org.apache.kafka.streams.StreamsBuilder; -import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.TestInputTopic; import org.apache.kafka.streams.TestOutputTopic; import org.apache.kafka.streams.Topology; @@ -54,7 +53,6 @@ import java.util.Random; import java.util.Set; -import static org.apache.kafka.test.StreamsTestUtils.getMetricByName; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; @@ -514,16 +512,7 @@ public void shouldNotThrowIllegalStateExceptionWhenMultiCacheEvictions() { } @Test - public void shouldLogAndMeterSkippedRecordsDueToNullLeftKeyWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterSkippedRecordsDueToNullLeftKey(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeterSkippedRecordsDueToNullLeftKeyWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterSkippedRecordsDueToNullLeftKey(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtInMetricsVersion) { + public void shouldLogAndMeterSkippedRecordsDueToNullLeftKey() { final StreamsBuilder builder = new StreamsBuilder(); @SuppressWarnings("unchecked") @@ -533,7 +522,6 @@ private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtI null ).get(); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); final MockProcessorContext context = new MockProcessorContext(props); context.setRecordMetadata("left", -1, -2, null, -3); join.init(context); @@ -546,13 +534,6 @@ private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtI hasItem("Skipping record due to null key. change=[(new<-old)] topic=[left] partition=[-1] offset=[-2]") ); } - - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals( - 1.0, - getMetricByName(context.metrics().metrics(), "skipped-records-total", "stream-metrics").metricValue() - ); - } } private void assertOutputKeyValueTimestamp(final TestOutputTopic outputTopic, diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableOuterJoinTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableOuterJoinTest.java index 9f99ae6028ea1..40da184f9cee6 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableOuterJoinTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableOuterJoinTest.java @@ -19,7 +19,6 @@ import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.streams.KeyValueTimestamp; import org.apache.kafka.streams.StreamsBuilder; -import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.TestInputTopic; import org.apache.kafka.streams.TestOutputTopic; import org.apache.kafka.streams.Topology; @@ -45,7 +44,6 @@ import java.util.Properties; import java.util.Set; -import static org.apache.kafka.test.StreamsTestUtils.getMetricByName; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; @@ -405,16 +403,7 @@ public void testSendingOldValue() { } @Test - public void shouldLogAndMeterSkippedRecordsDueToNullLeftKeyWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterSkippedRecordsDueToNullLeftKey(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeterSkippedRecordsDueToNullLeftKeyWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterSkippedRecordsDueToNullLeftKey(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtInMetricsVersion) { + public void shouldLogAndMeterSkippedRecordsDueToNullLeftKey() { final StreamsBuilder builder = new StreamsBuilder(); @SuppressWarnings("unchecked") @@ -424,7 +413,6 @@ private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtI null ).get(); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); final MockProcessorContext context = new MockProcessorContext(props); context.setRecordMetadata("left", -1, -2, null, -3); join.init(context); @@ -437,13 +425,6 @@ private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtI hasItem("Skipping record due to null key. change=[(new<-old)] topic=[left] partition=[-1] offset=[-2]") ); } - - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals( - 1.0, - getMetricByName(context.metrics().metrics(), "skipped-records-total", "stream-metrics").metricValue() - ); - } } private void assertOutputKeyValueTimestamp(final TestOutputTopic outputTopic, diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableRightJoinTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableRightJoinTest.java index 11d8cc80ab6f6..c5e211d0c39c5 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableRightJoinTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableKTableRightJoinTest.java @@ -28,26 +28,15 @@ import java.util.Properties; -import static org.apache.kafka.test.StreamsTestUtils.getMetricByName; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; public class KTableKTableRightJoinTest { private final Properties props = StreamsTestUtils.getStreamsConfig(Serdes.String(), Serdes.String()); - @Test - public void shouldLogAndMeterSkippedRecordsDueToNullLeftKeyWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeterSkippedRecordsDueToNullLeftKey(StreamsConfig.METRICS_0100_TO_24); - } - @Test public void shouldLogAndMeterSkippedRecordsDueToNullLeftKeyWithBuiltInMetricsVersionLatest() { - shouldLogAndMeterSkippedRecordsDueToNullLeftKey(StreamsConfig.METRICS_LATEST); - } - - private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtInMetricsVersion) { final StreamsBuilder builder = new StreamsBuilder(); @SuppressWarnings("unchecked") @@ -57,7 +46,7 @@ private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtI null ).get(); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); + props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, StreamsConfig.METRICS_LATEST); final MockProcessorContext context = new MockProcessorContext(props); context.setRecordMetadata("left", -1, -2, null, -3); join.init(context); @@ -70,12 +59,5 @@ private void shouldLogAndMeterSkippedRecordsDueToNullLeftKey(final String builtI hasItem("Skipping record due to null key. change=[(new<-old)] topic=[left] partition=[-1] offset=[-2]") ); } - - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals( - 1.0, - getMetricByName(context.metrics().metrics(), "skipped-records-total", "stream-metrics").metricValue() - ); - } } } diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableReduceTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableReduceTest.java index b360151f444e0..87d6e8745b3aa 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableReduceTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableReduceTest.java @@ -36,7 +36,7 @@ public class KTableReduceTest { @Test public void shouldAddAndSubtract() { - final InternalMockProcessorContext context = new InternalMockProcessorContext(); + final InternalMockProcessorContext>> context = new InternalMockProcessorContext<>(); final Processor>> reduceProcessor = new KTableReduce>( diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableSourceTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableSourceTest.java index 02590f6063c18..1091d54deb4fc 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableSourceTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/KTableSourceTest.java @@ -23,7 +23,6 @@ import org.apache.kafka.common.serialization.StringSerializer; import org.apache.kafka.streams.KeyValueTimestamp; import org.apache.kafka.streams.StreamsBuilder; -import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.TestInputTopic; import org.apache.kafka.streams.TestOutputTopic; import org.apache.kafka.streams.Topology; @@ -50,7 +49,6 @@ import static java.util.Arrays.asList; import static org.apache.kafka.test.StreamsTestUtils.getMetricByName; -import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -131,58 +129,12 @@ public void testKTableSourceEmitOnChange() { } } - @Test - public void kTableShouldLogAndMeterOnSkippedRecordsWithBuiltInMetrics0100To24() { - kTableShouldLogAndMeterOnSkippedRecords(StreamsConfig.METRICS_0100_TO_24); - } - - @Test - public void kTableShouldLogAndMeterOnSkippedRecordsWithBuiltInMetricsLatest() { - kTableShouldLogAndMeterOnSkippedRecords(StreamsConfig.METRICS_LATEST); - } - - private void kTableShouldLogAndMeterOnSkippedRecords(final String builtInMetricsVersion) { - final StreamsBuilder builder = new StreamsBuilder(); - final String topic = "topic"; - builder.table(topic, stringConsumed); - - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); - - try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KTableSource.class); - final TopologyTestDriver driver = new TopologyTestDriver(builder.build(), props)) { - - final TestInputTopic inputTopic = - driver.createInputTopic( - topic, - new StringSerializer(), - new StringSerializer(), - Instant.ofEpochMilli(0L), - Duration.ZERO - ); - inputTopic.pipeInput(null, "value"); - - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals( - 1.0, - getMetricByName(driver.metrics(), "skipped-records-total", "stream-metrics").metricValue() - ); - } - - assertThat( - appender.getMessages(), - hasItem("Skipping record due to null key. topic=[topic] partition=[0] offset=[0]") - ); - } - } - @Test public void kTableShouldLogAndMeterOnSkippedRecords() { final StreamsBuilder builder = new StreamsBuilder(); final String topic = "topic"; builder.table(topic, stringConsumed); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, StreamsConfig.METRICS_0100_TO_24); - try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(KTableSource.class); final TopologyTestDriver driver = new TopologyTestDriver(builder.build(), props)) { @@ -196,11 +148,6 @@ public void kTableShouldLogAndMeterOnSkippedRecords() { ); inputTopic.pipeInput(null, "value"); - assertThat( - getMetricByName(driver.metrics(), "skipped-records-total", "stream-metrics").metricValue(), - equalTo(1.0) - ); - assertThat( appender.getMessages(), hasItem("Skipping record due to null key. topic=[topic] partition=[0] offset=[0]") diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/SessionCacheFlushListenerTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/SessionCacheFlushListenerTest.java index b25febf94072b..c60bcf4bbb3a7 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/SessionCacheFlushListenerTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/SessionCacheFlushListenerTest.java @@ -30,7 +30,7 @@ public class SessionCacheFlushListenerTest { @Test public void shouldForwardKeyNewValueOldValueAndTimestamp() { - final InternalProcessorContext context = mock(InternalProcessorContext.class); + final InternalProcessorContext, Change> context = mock(InternalProcessorContext.class); expect(context.currentNode()).andReturn(null).anyTimes(); context.setCurrentNode(null); context.setCurrentNode(null); diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/TimestampedCacheFlushListenerTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/TimestampedCacheFlushListenerTest.java index 38ef5c68401a9..7c25b2ec51ee1 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/TimestampedCacheFlushListenerTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/TimestampedCacheFlushListenerTest.java @@ -17,6 +17,7 @@ package org.apache.kafka.streams.kstream.internals; import org.apache.kafka.streams.processor.To; +import org.apache.kafka.streams.processor.api.ProcessorContext; import org.apache.kafka.streams.processor.internals.InternalProcessorContext; import org.apache.kafka.streams.state.ValueAndTimestamp; import org.junit.Test; @@ -31,7 +32,7 @@ public class TimestampedCacheFlushListenerTest { @Test public void shouldForwardValueTimestampIfNewValueExists() { - final InternalProcessorContext context = mock(InternalProcessorContext.class); + final InternalProcessorContext> context = mock(InternalProcessorContext.class); expect(context.currentNode()).andReturn(null).anyTimes(); context.setCurrentNode(null); context.setCurrentNode(null); @@ -42,7 +43,7 @@ public void shouldForwardValueTimestampIfNewValueExists() { expectLastCall(); replay(context); - new TimestampedCacheFlushListener<>(context).apply( + new TimestampedCacheFlushListener<>((ProcessorContext>) context).apply( "key", ValueAndTimestamp.make("newValue", 42L), ValueAndTimestamp.make("oldValue", 21L), @@ -53,7 +54,7 @@ public void shouldForwardValueTimestampIfNewValueExists() { @Test public void shouldForwardParameterTimestampIfNewValueIsNull() { - final InternalProcessorContext context = mock(InternalProcessorContext.class); + final InternalProcessorContext> context = mock(InternalProcessorContext.class); expect(context.currentNode()).andReturn(null).anyTimes(); context.setCurrentNode(null); context.setCurrentNode(null); @@ -64,7 +65,7 @@ public void shouldForwardParameterTimestampIfNewValueIsNull() { expectLastCall(); replay(context); - new TimestampedCacheFlushListener<>(context).apply( + new TimestampedCacheFlushListener<>((ProcessorContext>) context).apply( "key", null, ValueAndTimestamp.make("oldValue", 21L), diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/TimestampedTupleForwarderTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/TimestampedTupleForwarderTest.java index 52a5fcf24d4ce..89b732ee59fa1 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/TimestampedTupleForwarderTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/TimestampedTupleForwarderTest.java @@ -16,9 +16,9 @@ */ package org.apache.kafka.streams.kstream.internals; -import org.apache.kafka.streams.processor.ProcessorContext; import org.apache.kafka.streams.processor.StateStore; import org.apache.kafka.streams.processor.To; +import org.apache.kafka.streams.processor.internals.InternalProcessorContext; import org.apache.kafka.streams.state.ValueAndTimestamp; import org.apache.kafka.streams.state.internals.WrappedStateStore; import org.junit.Test; @@ -44,7 +44,12 @@ private void setFlushListener(final boolean sendOldValues) { expect(store.setFlushListener(flushListener, sendOldValues)).andReturn(false); replay(store); - new TimestampedTupleForwarder<>(store, null, flushListener, sendOldValues); + new TimestampedTupleForwarder<>( + store, + (org.apache.kafka.streams.processor.api.ProcessorContext>) null, + flushListener, + sendOldValues + ); verify(store); } @@ -57,7 +62,7 @@ public void shouldForwardRecordsIfWrappedStateStoreDoesNotCache() { private void shouldForwardRecordsIfWrappedStateStoreDoesNotCache(final boolean sendOldValues) { final WrappedStateStore store = mock(WrappedStateStore.class); - final ProcessorContext context = mock(ProcessorContext.class); + final InternalProcessorContext> context = mock(InternalProcessorContext.class); expect(store.setFlushListener(null, sendOldValues)).andReturn(false); if (sendOldValues) { @@ -71,7 +76,12 @@ private void shouldForwardRecordsIfWrappedStateStoreDoesNotCache(final boolean s replay(store, context); final TimestampedTupleForwarder forwarder = - new TimestampedTupleForwarder<>(store, context, null, sendOldValues); + new TimestampedTupleForwarder<>( + store, + (org.apache.kafka.streams.processor.api.ProcessorContext>) context, + null, + sendOldValues + ); forwarder.maybeForward("key1", "newValue1", "oldValue1"); forwarder.maybeForward("key2", "newValue2", "oldValue2", 42L); @@ -81,13 +91,18 @@ private void shouldForwardRecordsIfWrappedStateStoreDoesNotCache(final boolean s @Test public void shouldNotForwardRecordsIfWrappedStateStoreDoesCache() { final WrappedStateStore store = mock(WrappedStateStore.class); - final ProcessorContext context = mock(ProcessorContext.class); + final InternalProcessorContext> context = mock(InternalProcessorContext.class); expect(store.setFlushListener(null, false)).andReturn(true); replay(store, context); final TimestampedTupleForwarder forwarder = - new TimestampedTupleForwarder<>(store, context, null, false); + new TimestampedTupleForwarder<>( + store, + (org.apache.kafka.streams.processor.api.ProcessorContext>) context, + null, + false + ); forwarder.maybeForward("key", "newValue", "oldValue"); forwarder.maybeForward("key", "newValue", "oldValue", 42L); diff --git a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/suppress/KTableSuppressProcessorMetricsTest.java b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/suppress/KTableSuppressProcessorMetricsTest.java index 3b46765fe20fe..3789ad268daef 100644 --- a/streams/src/test/java/org/apache/kafka/streams/kstream/internals/suppress/KTableSuppressProcessorMetricsTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/kstream/internals/suppress/KTableSuppressProcessorMetricsTest.java @@ -55,17 +55,6 @@ public class KTableSuppressProcessorMetricsTest { private Properties streamsConfig = StreamsTestUtils.getStreamsConfig(); private final String threadId = Thread.currentThread().getName(); - private final MetricName evictionTotalMetric0100To24 = new MetricName( - "suppression-emit-total", - "stream-processor-node-metrics", - "The total number of emitted records from the suppression buffer", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", TASK_ID.toString()), - mkEntry("processor-node-id", "testNode") - ) - ); - private final MetricName evictionTotalMetricLatest = new MetricName( "suppression-emit-total", "stream-processor-node-metrics", @@ -77,17 +66,6 @@ public class KTableSuppressProcessorMetricsTest { ) ); - private final MetricName evictionRateMetric0100To24 = new MetricName( - "suppression-emit-rate", - "stream-processor-node-metrics", - "The average number of emitted records from the suppression buffer per second", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", TASK_ID.toString()), - mkEntry("processor-node-id", "testNode") - ) - ); - private final MetricName evictionRateMetricLatest = new MetricName( "suppression-emit-rate", "stream-processor-node-metrics", @@ -99,17 +77,6 @@ public class KTableSuppressProcessorMetricsTest { ) ); - private final MetricName bufferSizeAvgMetric0100To24 = new MetricName( - "suppression-buffer-size-avg", - "stream-buffer-metrics", - "The average size of buffered records", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", TASK_ID.toString()), - mkEntry("buffer-id", "test-store") - ) - ); - private final MetricName bufferSizeAvgMetricLatest = new MetricName( "suppression-buffer-size-avg", "stream-state-metrics", @@ -121,28 +88,6 @@ public class KTableSuppressProcessorMetricsTest { ) ); - private final MetricName bufferSizeCurrentMetric = new MetricName( - "suppression-buffer-size-current", - "stream-buffer-metrics", - "The current size of buffered records", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", TASK_ID.toString()), - mkEntry("buffer-id", "test-store") - ) - ); - - private final MetricName bufferSizeMaxMetric0100To24 = new MetricName( - "suppression-buffer-size-max", - "stream-buffer-metrics", - "The maximum size of buffered records", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", TASK_ID.toString()), - mkEntry("buffer-id", "test-store") - ) - ); - private final MetricName bufferSizeMaxMetricLatest = new MetricName( "suppression-buffer-size-max", "stream-state-metrics", @@ -154,17 +99,6 @@ public class KTableSuppressProcessorMetricsTest { ) ); - private final MetricName bufferCountAvgMetric0100To24 = new MetricName( - "suppression-buffer-count-avg", - "stream-buffer-metrics", - "The average count of buffered records", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", TASK_ID.toString()), - mkEntry("buffer-id", "test-store") - ) - ); - private final MetricName bufferCountAvgMetricLatest = new MetricName( "suppression-buffer-count-avg", "stream-state-metrics", @@ -176,28 +110,6 @@ public class KTableSuppressProcessorMetricsTest { ) ); - private final MetricName bufferCountCurrentMetric = new MetricName( - "suppression-buffer-count-current", - "stream-buffer-metrics", - "The current count of buffered records", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", TASK_ID.toString()), - mkEntry("buffer-id", "test-store") - ) - ); - - private final MetricName bufferCountMaxMetric0100To24 = new MetricName( - "suppression-buffer-count-max", - "stream-buffer-metrics", - "The maximum count of buffered records", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", TASK_ID.toString()), - mkEntry("buffer-id", "test-store") - ) - ); - private final MetricName bufferCountMaxMetricLatest = new MetricName( "suppression-buffer-count-max", "stream-state-metrics", @@ -211,15 +123,6 @@ public class KTableSuppressProcessorMetricsTest { @Test public void shouldRecordMetricsWithBuiltInMetricsVersionLatest() { - shouldRecordMetrics(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldRecordMetricsWithBuiltInMetricsVersion0100To24() { - shouldRecordMetrics(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldRecordMetrics(final String builtInMetricsVersion) { final String storeName = "test-store"; final StateStore buffer = new InMemoryTimeOrderedKeyValueBuffer.Builder<>( @@ -237,7 +140,7 @@ private void shouldRecordMetrics(final String builtInMetricsVersion) { mock ).get(); - streamsConfig.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); + streamsConfig.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, StreamsConfig.METRICS_LATEST); final MockInternalProcessorContext context = new MockInternalProcessorContext(streamsConfig, TASK_ID, TestUtils.tempDirectory()); final Time time = new SystemTime(); @@ -253,18 +156,12 @@ private void shouldRecordMetrics(final String builtInMetricsVersion) { final Change value = new Change<>(null, ARBITRARY_LONG); processor.process(key, value); - final MetricName evictionRateMetric = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? evictionRateMetric0100To24 : evictionRateMetricLatest; - final MetricName evictionTotalMetric = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? evictionTotalMetric0100To24 : evictionTotalMetricLatest; - final MetricName bufferSizeAvgMetric = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? bufferSizeAvgMetric0100To24 : bufferSizeAvgMetricLatest; - final MetricName bufferSizeMaxMetric = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? bufferSizeMaxMetric0100To24 : bufferSizeMaxMetricLatest; - final MetricName bufferCountAvgMetric = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? bufferCountAvgMetric0100To24 : bufferCountAvgMetricLatest; - final MetricName bufferCountMaxMetric = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? bufferCountMaxMetric0100To24 : bufferCountMaxMetricLatest; + final MetricName evictionRateMetric = evictionRateMetricLatest; + final MetricName evictionTotalMetric = evictionTotalMetricLatest; + final MetricName bufferSizeAvgMetric = bufferSizeAvgMetricLatest; + final MetricName bufferSizeMaxMetric = bufferSizeMaxMetricLatest; + final MetricName bufferCountAvgMetric = bufferCountAvgMetricLatest; + final MetricName bufferCountMaxMetric = bufferCountMaxMetricLatest; { final Map metrics = context.metrics().metrics(); @@ -275,10 +172,6 @@ private void shouldRecordMetrics(final String builtInMetricsVersion) { verifyMetric(metrics, bufferSizeMaxMetric, is(43.0)); verifyMetric(metrics, bufferCountAvgMetric, is(0.5)); verifyMetric(metrics, bufferCountMaxMetric, is(1.0)); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - verifyMetric(metrics, bufferSizeCurrentMetric, is(43.0)); - verifyMetric(metrics, bufferCountCurrentMetric, is(1.0)); - } } context.setRecordMetadata("", 0, 1L, null, timestamp + 1); @@ -293,10 +186,6 @@ private void shouldRecordMetrics(final String builtInMetricsVersion) { verifyMetric(metrics, bufferSizeMaxMetric, is(82.0)); verifyMetric(metrics, bufferCountAvgMetric, is(1.0)); verifyMetric(metrics, bufferCountMaxMetric, is(2.0)); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - verifyMetric(metrics, bufferSizeCurrentMetric, is(39.0)); - verifyMetric(metrics, bufferCountCurrentMetric, is(1.0)); - } } } diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/AbstractProcessorContextTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/AbstractProcessorContextTest.java index b813422ee3fd4..551111294f1e3 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/AbstractProcessorContextTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/AbstractProcessorContextTest.java @@ -171,7 +171,7 @@ public void appConfigsShouldReturnUnrecognizedValues() { ); } - private static class TestProcessorContext extends AbstractProcessorContext { + private static class TestProcessorContext extends AbstractProcessorContext { static Properties config; static { config = getStreamsConfig(); @@ -242,7 +242,7 @@ public void registerCacheFlushListener(final String namespace, final DirtyEntryF @Override public String changelogFor(final String storeName) { - return ProcessorStateManager.storeChangelogTopic(applicationId(), storeName); + return ProcessorStateManager.storeChangelogTopic(applicationId(), storeName, taskId().namedTopology()); } } } diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/ActiveTaskCreatorTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/ActiveTaskCreatorTest.java index 54c92dfef4caf..b689dc18c51b4 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/ActiveTaskCreatorTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/ActiveTaskCreatorTest.java @@ -462,9 +462,10 @@ private void createTasks() { expect(topology.sources()).andStubReturn(Collections.singleton(sourceNode)); replay(builder, stateDirectory, topology, sourceNode); + final StreamsConfig config = new StreamsConfig(properties); activeTaskCreator = new ActiveTaskCreator( - builder, - new StreamsConfig(properties), + new TopologyMetadata(builder, config), + config, streamsMetrics, stateDirectory, changeLogReader, diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStateManagerImplTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStateManagerImplTest.java index ee5ca80efdeb0..a077741a56379 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStateManagerImplTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStateManagerImplTest.java @@ -134,7 +134,7 @@ public void before() { put(StreamsConfig.STATE_DIR_CONFIG, TestUtils.tempDirectory().getPath()); } }); - stateDirectory = new StateDirectory(streamsConfig, time, true); + stateDirectory = new StateDirectory(streamsConfig, time, true, false); consumer = new MockConsumer<>(OffsetResetStrategy.NONE); stateManager = new GlobalStateManagerImpl( new LogContext("test"), diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStateTaskTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStateTaskTest.java index e4bc600133341..31be9dc2a4db1 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStateTaskTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStateTaskTest.java @@ -61,10 +61,10 @@ public class GlobalStateTaskTest { private final String topic2 = "t2"; private final TopicPartition t1 = new TopicPartition(topic1, 1); private final TopicPartition t2 = new TopicPartition(topic2, 1); - private final MockSourceNode sourceOne = new MockSourceNode<>( + private final MockSourceNode sourceOne = new MockSourceNode<>( new StringDeserializer(), new StringDeserializer()); - private final MockSourceNode sourceTwo = new MockSourceNode<>( + private final MockSourceNode sourceTwo = new MockSourceNode<>( new IntegerDeserializer(), new IntegerDeserializer()); private final MockProcessorNode processorOne = new MockProcessorNode<>(); @@ -81,7 +81,7 @@ public class GlobalStateTaskTest { @Before public void before() { final Set storeNames = Utils.mkSet("t1-store", "t2-store"); - final Map> sourceByTopics = new HashMap<>(); + final Map> sourceByTopics = new HashMap<>(); sourceByTopics.put(topic1, sourceOne); sourceByTopics.put(topic2, sourceTwo); final Map storeToTopic = new HashMap<>(); diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadTest.java index a2c980ecfe287..9426b06d025dc 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadTest.java @@ -109,7 +109,7 @@ public String newStoreName(final String prefix) { builder.rewriteTopology(config).buildGlobalStateTopology(), config, mockConsumer, - new StateDirectory(config, time, true), + new StateDirectory(config, time, true, false), 0, new StreamsMetricsImpl(new Metrics(), "test-client", StreamsConfig.METRICS_LATEST, time), time, @@ -148,7 +148,7 @@ public List partitionsFor(final String topic) { builder.buildGlobalStateTopology(), config, mockConsumer, - new StateDirectory(config, time, true), + new StateDirectory(config, time, true, false), 0, new StreamsMetricsImpl(new Metrics(), "test-client", StreamsConfig.METRICS_LATEST, time), time, diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/HighAvailabilityStreamsPartitionAssignorTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/HighAvailabilityStreamsPartitionAssignorTest.java index d020cb37f1367..56df1faf25a20 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/HighAvailabilityStreamsPartitionAssignorTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/HighAvailabilityStreamsPartitionAssignorTest.java @@ -108,6 +108,7 @@ public class HighAvailabilityStreamsPartitionAssignorTest { private Admin adminClient; private StreamsConfig streamsConfig = new StreamsConfig(configProps()); private final InternalTopologyBuilder builder = new InternalTopologyBuilder(); + private TopologyMetadata topologyMetadata = new TopologyMetadata(builder, streamsConfig); private final StreamsMetadataState streamsMetadataState = EasyMock.createNiceMock(StreamsMetadataState.class); private final Map subscriptions = new HashMap<>(); @@ -147,11 +148,10 @@ private void createMockTaskManager(final Set activeTasks) { private void createMockTaskManager(final Map taskOffsetSums) { taskManager = EasyMock.createNiceMock(TaskManager.class); - expect(taskManager.builder()).andReturn(builder).anyTimes(); + expect(taskManager.topologyMetadata()).andStubReturn(topologyMetadata); expect(taskManager.getTaskOffsetSums()).andReturn(taskOffsetSums).anyTimes(); expect(taskManager.processId()).andReturn(UUID_1).anyTimes(); - builder.setApplicationId(APPLICATION_ID); - builder.buildTopology(); + topologyMetadata.buildAndRewriteTopology(); } // If you don't care about setting the end offsets for each specific topic partition, the helper method diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/InternalTopologyBuilderTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/InternalTopologyBuilderTest.java index ef5bebb5d8201..4cd58909656b9 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/InternalTopologyBuilderTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/InternalTopologyBuilderTest.java @@ -16,6 +16,7 @@ */ package org.apache.kafka.streams.processor.internals; +import org.apache.kafka.clients.consumer.OffsetResetStrategy; import org.apache.kafka.common.config.TopicConfig; import org.apache.kafka.common.serialization.Serde; import org.apache.kafka.common.serialization.Serdes; @@ -87,8 +88,8 @@ public void shouldAddSourceWithOffsetReset() { builder.addSource(Topology.AutoOffsetReset.LATEST, "source2", null, null, null, latestTopic); builder.initializeSubscription(); - assertTrue(builder.earliestResetTopicsPattern().matcher(earliestTopic).matches()); - assertTrue(builder.latestResetTopicsPattern().matcher(latestTopic).matches()); + assertThat(builder.offsetResetStrategy(earliestTopic), equalTo(OffsetResetStrategy.EARLIEST)); + assertThat(builder.offsetResetStrategy(latestTopic), equalTo(OffsetResetStrategy.LATEST)); } @Test @@ -100,8 +101,8 @@ public void shouldAddSourcePatternWithOffsetReset() { builder.addSource(Topology.AutoOffsetReset.LATEST, "source2", null, null, null, Pattern.compile(latestTopicPattern)); builder.initializeSubscription(); - assertTrue(builder.earliestResetTopicsPattern().matcher("earliestTestTopic").matches()); - assertTrue(builder.latestResetTopicsPattern().matcher("latestTestTopic").matches()); + assertThat(builder.offsetResetStrategy("earliestTestTopic"), equalTo(OffsetResetStrategy.EARLIEST)); + assertThat(builder.offsetResetStrategy("latestTestTopic"), equalTo(OffsetResetStrategy.LATEST)); } @Test @@ -110,8 +111,8 @@ public void shouldAddSourceWithoutOffsetReset() { builder.initializeSubscription(); assertEquals(Collections.singletonList("test-topic"), builder.sourceTopicCollection()); - assertEquals(builder.earliestResetTopicsPattern().pattern(), ""); - assertEquals(builder.latestResetTopicsPattern().pattern(), ""); + + assertThat(builder.offsetResetStrategy("test-topic"), equalTo(OffsetResetStrategy.NONE)); } @Test @@ -121,9 +122,9 @@ public void shouldAddPatternSourceWithoutOffsetReset() { builder.addSource(null, "source", null, stringSerde.deserializer(), stringSerde.deserializer(), Pattern.compile("test-.*")); builder.initializeSubscription(); - assertEquals(expectedPattern.pattern(), builder.sourceTopicPattern().pattern()); - assertEquals(builder.earliestResetTopicsPattern().pattern(), ""); - assertEquals(builder.latestResetTopicsPattern().pattern(), ""); + assertThat(expectedPattern.pattern(), builder.sourceTopicsPatternString(), equalTo("test-.*")); + + assertThat(builder.offsetResetStrategy("test-topic"), equalTo(OffsetResetStrategy.NONE)); } @Test @@ -303,8 +304,9 @@ public void testPatternAndNameSourceTopics() { builder.initializeSubscription(); final Pattern expectedPattern = Pattern.compile("X-topic-3|topic-1|topic-2|topic-4|topic-5"); + final String patternString = builder.sourceTopicsPatternString(); - assertEquals(expectedPattern.pattern(), builder.sourceTopicPattern().pattern()); + assertEquals(expectedPattern.pattern(), Pattern.compile(patternString).pattern()); } @Test @@ -326,7 +328,9 @@ public void testPatternSourceTopicsWithGlobalTopics() { final Pattern expectedPattern = Pattern.compile("topic-1|topic-2"); - assertThat(builder.sourceTopicPattern().pattern(), equalTo(expectedPattern.pattern())); + final String patternString = builder.sourceTopicsPatternString(); + + assertEquals(expectedPattern.pattern(), Pattern.compile(patternString).pattern()); } @Test @@ -354,7 +358,9 @@ public void testPatternSourceTopic() { final Pattern expectedPattern = Pattern.compile("topic-\\d"); builder.addSource(null, "source-1", null, null, null, expectedPattern); builder.initializeSubscription(); - assertEquals(expectedPattern.pattern(), builder.sourceTopicPattern().pattern()); + final String patternString = builder.sourceTopicsPatternString(); + + assertEquals(expectedPattern.pattern(), Pattern.compile(patternString).pattern()); } @Test @@ -363,7 +369,9 @@ public void testAddMoreThanOnePatternSourceNode() { builder.addSource(null, "source-1", null, null, null, Pattern.compile("topics[A-Z]")); builder.addSource(null, "source-2", null, null, null, Pattern.compile(".*-\\d")); builder.initializeSubscription(); - assertEquals(expectedPattern.pattern(), builder.sourceTopicPattern().pattern()); + final String patternString = builder.sourceTopicsPatternString(); + + assertEquals(expectedPattern.pattern(), Pattern.compile(patternString).pattern()); } @Test @@ -372,7 +380,9 @@ public void testSubscribeTopicNameAndPattern() { builder.addSource(null, "source-1", null, null, null, "topic-foo", "topic-bar"); builder.addSource(null, "source-2", null, null, null, Pattern.compile(".*-\\d")); builder.initializeSubscription(); - assertEquals(expectedPattern.pattern(), builder.sourceTopicPattern().pattern()); + final String patternString = builder.sourceTopicsPatternString(); + + assertEquals(expectedPattern.pattern(), Pattern.compile(patternString).pattern()); } @Test @@ -616,9 +626,9 @@ public void testTopicGroupsByStateStore() { final Map topicGroups = builder.topicGroups(); final Map expectedTopicGroups = new HashMap<>(); - final String store1 = ProcessorStateManager.storeChangelogTopic("X", "store-1"); - final String store2 = ProcessorStateManager.storeChangelogTopic("X", "store-2"); - final String store3 = ProcessorStateManager.storeChangelogTopic("X", "store-3"); + final String store1 = ProcessorStateManager.storeChangelogTopic("X", "store-1", builder.namedTopology()); + final String store2 = ProcessorStateManager.storeChangelogTopic("X", "store-2", builder.namedTopology()); + final String store3 = ProcessorStateManager.storeChangelogTopic("X", "store-3", builder.namedTopology()); expectedTopicGroups.put(SUBTOPOLOGY_0, new InternalTopologyBuilder.TopicsInfo( Collections.emptySet(), mkSet("topic-1", "topic-1x", "topic-2"), Collections.emptyMap(), diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/NamedTopologyTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/NamedTopologyTest.java new file mode 100644 index 0000000000000..48ab10bec59db --- /dev/null +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/NamedTopologyTest.java @@ -0,0 +1,311 @@ +/* + * 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.streams.processor.internals; + +import org.apache.kafka.streams.KafkaClientSupplier; +import org.apache.kafka.streams.StreamsConfig; +import org.apache.kafka.streams.errors.TopologyException; +import org.apache.kafka.streams.integration.utils.IntegrationTestUtils; +import org.apache.kafka.streams.kstream.Materialized; +import org.apache.kafka.streams.processor.internals.namedtopology.KafkaStreamsNamedTopologyWrapper; +import org.apache.kafka.streams.processor.internals.namedtopology.NamedTopology; +import org.apache.kafka.streams.processor.internals.namedtopology.NamedTopologyStreamsBuilder; +import org.apache.kafka.streams.state.Stores; +import org.apache.kafka.test.TestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import java.time.Duration; +import java.util.Properties; +import java.util.regex.Pattern; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertThrows; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + +public class NamedTopologyTest { + final KafkaClientSupplier clientSupplier = new DefaultKafkaClientSupplier(); + final Properties props = configProps(); + + final NamedTopologyStreamsBuilder builder1 = new NamedTopologyStreamsBuilder("topology-1"); + final NamedTopologyStreamsBuilder builder2 = new NamedTopologyStreamsBuilder("topology-2"); + final NamedTopologyStreamsBuilder builder3 = new NamedTopologyStreamsBuilder("topology-3"); + + KafkaStreamsNamedTopologyWrapper streams; + + @Before + public void setup() { + builder1.stream("input-1"); + builder2.stream("input-2"); + builder3.stream("input-3"); + } + + @After + public void cleanup() { + if (streams != null) { + streams.close(); + } + } + + private static Properties configProps() { + final Properties props = new Properties(); + props.put(StreamsConfig.APPLICATION_ID_CONFIG, "Named-Topology-App"); + props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:2018"); + props.put(StreamsConfig.STATE_DIR_CONFIG, TestUtils.tempDirectory().getPath()); + return props; + } + + @Test + public void shouldThrowIllegalArgumentOnIllegalName() { + assertThrows(IllegalArgumentException.class, () -> new NamedTopologyStreamsBuilder("__not-allowed__")); + } + + @Test + public void shouldStartUpAndGoToRunningWithEmptyNamedTopology() throws Exception { + streams = new KafkaStreamsNamedTopologyWrapper(props, clientSupplier); + IntegrationTestUtils.startApplicationAndWaitUntilRunning(singletonList(streams), Duration.ofSeconds(15)); + } + + @Test + public void shouldBuildSingleNamedTopology() { + builder1.stream("stream-1").filter((k, v) -> !k.equals(v)).to("output-1"); + + streams = new KafkaStreamsNamedTopologyWrapper(builder1.buildNamedTopology(props), props, clientSupplier); + } + + @Test + public void shouldBuildMultipleIdenticalNamedTopologyWithRepartition() { + builder1.stream("stream-1").selectKey((k, v) -> v).groupByKey().count().toStream().to("output-1"); + builder2.stream("stream-2").selectKey((k, v) -> v).groupByKey().count().toStream().to("output-2"); + builder3.stream("stream-3").selectKey((k, v) -> v).groupByKey().count().toStream().to("output-3"); + + streams = new KafkaStreamsNamedTopologyWrapper( + asList( + builder1.buildNamedTopology(props), + builder2.buildNamedTopology(props), + builder3.buildNamedTopology(props)), + props, + clientSupplier + ); + } + + @Test + public void shouldReturnTopologyByName() { + final NamedTopology topology1 = builder1.buildNamedTopology(props); + final NamedTopology topology2 = builder2.buildNamedTopology(props); + final NamedTopology topology3 = builder3.buildNamedTopology(props); + streams = new KafkaStreamsNamedTopologyWrapper(asList(topology1, topology2, topology3), props, clientSupplier); + assertThat(streams.getTopologyByName("topology-1"), equalTo(topology1)); + assertThat(streams.getTopologyByName("topology-2"), equalTo(topology2)); + assertThat(streams.getTopologyByName("topology-3"), equalTo(topology3)); + } + + @Test + public void shouldThrowIllegalArgumentWhenLookingUpNonExistentTopologyByName() { + streams = new KafkaStreamsNamedTopologyWrapper(builder1.buildNamedTopology(props), props, clientSupplier); + assertThrows(IllegalArgumentException.class, () -> streams.getTopologyByName("non-existent-topology")); + } + + @Test + public void shouldAllowSameStoreNameToBeUsedByMultipleNamedTopologies() { + builder1.stream("stream-1").selectKey((k, v) -> v).groupByKey().count(Materialized.as(Stores.inMemoryKeyValueStore("store"))); + builder2.stream("stream-2").selectKey((k, v) -> v).groupByKey().count(Materialized.as(Stores.inMemoryKeyValueStore("store"))); + + streams = new KafkaStreamsNamedTopologyWrapper(asList( + builder1.buildNamedTopology(props), + builder2.buildNamedTopology(props)), + props, + clientSupplier + ); + } + + @Test + public void shouldThrowTopologyExceptionWhenMultipleNamedTopologiesCreateStreamFromSameInputTopic() { + builder1.stream("stream"); + builder2.stream("stream"); + + assertThrows( + TopologyException.class, + () -> streams = new KafkaStreamsNamedTopologyWrapper( + asList( + builder1.buildNamedTopology(props), + builder2.buildNamedTopology(props)), + props, + clientSupplier) + ); + } + + @Test + public void shouldThrowTopologyExceptionWhenMultipleNamedTopologiesCreateTableFromSameInputTopic() { + builder1.table("table"); + builder2.table("table"); + + assertThrows( + TopologyException.class, + () -> streams = new KafkaStreamsNamedTopologyWrapper( + asList( + builder1.buildNamedTopology(props), + builder2.buildNamedTopology(props)), + props, + clientSupplier) + ); + } + + @Test + public void shouldThrowTopologyExceptionWhenMultipleNamedTopologiesCreateStreamAndTableFromSameInputTopic() { + builder1.stream("input"); + builder2.table("input"); + + assertThrows( + TopologyException.class, + () -> streams = new KafkaStreamsNamedTopologyWrapper( + asList( + builder1.buildNamedTopology(props), + builder2.buildNamedTopology(props)), + props, + clientSupplier) + ); + } + + @Test + public void shouldThrowTopologyExceptionWhenMultipleNamedTopologiesCreateStreamFromOverlappingInputTopicCollection() { + builder1.stream("stream"); + builder2.stream(asList("unique-input", "stream")); + + assertThrows( + TopologyException.class, + () -> streams = new KafkaStreamsNamedTopologyWrapper( + asList( + builder1.buildNamedTopology(props), + builder2.buildNamedTopology(props)), + props, + clientSupplier) + ); + } + + @Test + public void shouldThrowTopologyExceptionWhenMultipleNamedTopologiesCreateStreamFromSamePattern() { + builder1.stream(Pattern.compile("some-regex")); + builder2.stream(Pattern.compile("some-regex")); + + assertThrows( + TopologyException.class, + () -> streams = new KafkaStreamsNamedTopologyWrapper( + asList( + builder1.buildNamedTopology(props), + builder2.buildNamedTopology(props)), + props, + clientSupplier) + ); + } + + @Test + public void shouldDescribeWithSingleNamedTopology() { + builder1.stream("input").filter((k, v) -> !k.equals(v)).to("output"); + streams = new KafkaStreamsNamedTopologyWrapper(builder1.buildNamedTopology(props), props, clientSupplier); + + assertThat( + streams.getFullTopologyDescription(), + equalTo( + "Topology - topology-1:\n" + + " Sub-topology: 0\n" + + " Source: KSTREAM-SOURCE-0000000000 (topics: [input-1])\n" + + " --> none\n" + + "\n" + + " Sub-topology: 1\n" + + " Source: KSTREAM-SOURCE-0000000001 (topics: [input])\n" + + " --> KSTREAM-FILTER-0000000002\n" + + " Processor: KSTREAM-FILTER-0000000002 (stores: [])\n" + + " --> KSTREAM-SINK-0000000003\n" + + " <-- KSTREAM-SOURCE-0000000001\n" + + " Sink: KSTREAM-SINK-0000000003 (topic: output)\n" + + " <-- KSTREAM-FILTER-0000000002\n\n") + ); + } + + @Test + public void shouldDescribeWithMultipleNamedTopologies() { + builder1.stream("stream-1").filter((k, v) -> !k.equals(v)).to("output-1"); + builder2.stream("stream-2").filter((k, v) -> !k.equals(v)).to("output-2"); + builder3.stream("stream-3").filter((k, v) -> !k.equals(v)).to("output-3"); + + streams = new KafkaStreamsNamedTopologyWrapper( + asList( + builder1.buildNamedTopology(props), + builder2.buildNamedTopology(props), + builder3.buildNamedTopology(props)), + props, + clientSupplier + ); + + assertThat( + streams.getFullTopologyDescription(), + equalTo( + "Topology - topology-1:\n" + + " Sub-topology: 0\n" + + " Source: KSTREAM-SOURCE-0000000000 (topics: [input-1])\n" + + " --> none\n" + + "\n" + + " Sub-topology: 1\n" + + " Source: KSTREAM-SOURCE-0000000001 (topics: [stream-1])\n" + + " --> KSTREAM-FILTER-0000000002\n" + + " Processor: KSTREAM-FILTER-0000000002 (stores: [])\n" + + " --> KSTREAM-SINK-0000000003\n" + + " <-- KSTREAM-SOURCE-0000000001\n" + + " Sink: KSTREAM-SINK-0000000003 (topic: output-1)\n" + + " <-- KSTREAM-FILTER-0000000002\n" + + "\n" + + "Topology - topology-2:\n" + + " Sub-topology: 0\n" + + " Source: KSTREAM-SOURCE-0000000000 (topics: [input-2])\n" + + " --> none\n" + + "\n" + + " Sub-topology: 1\n" + + " Source: KSTREAM-SOURCE-0000000001 (topics: [stream-2])\n" + + " --> KSTREAM-FILTER-0000000002\n" + + " Processor: KSTREAM-FILTER-0000000002 (stores: [])\n" + + " --> KSTREAM-SINK-0000000003\n" + + " <-- KSTREAM-SOURCE-0000000001\n" + + " Sink: KSTREAM-SINK-0000000003 (topic: output-2)\n" + + " <-- KSTREAM-FILTER-0000000002\n" + + "\n" + + "Topology - topology-3:\n" + + " Sub-topology: 0\n" + + " Source: KSTREAM-SOURCE-0000000000 (topics: [input-3])\n" + + " --> none\n" + + "\n" + + " Sub-topology: 1\n" + + " Source: KSTREAM-SOURCE-0000000001 (topics: [stream-3])\n" + + " --> KSTREAM-FILTER-0000000002\n" + + " Processor: KSTREAM-FILTER-0000000002 (stores: [])\n" + + " --> KSTREAM-SINK-0000000003\n" + + " <-- KSTREAM-SOURCE-0000000001\n" + + " Sink: KSTREAM-SINK-0000000003 (topic: output-3)\n" + + " <-- KSTREAM-FILTER-0000000002\n\n") + ); + } + + @Test + public void shouldDescribeWithEmptyNamedTopology() { + streams = new KafkaStreamsNamedTopologyWrapper(props, clientSupplier); + + assertThat(streams.getFullTopologyDescription(), equalTo("")); + } +} diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorNodeTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorNodeTest.java index 84bdf51cc8893..33d5c4d909caa 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorNodeTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorNodeTest.java @@ -98,64 +98,36 @@ public void close() { @Test public void testMetricsWithBuiltInMetricsVersionLatest() { - testMetrics(StreamsConfig.METRICS_LATEST); - } - - @Test - public void testMetricsWithBuiltInMetricsVersion0100To24() { - testMetrics(StreamsConfig.METRICS_0100_TO_24); - } - - private void testMetrics(final String builtInMetricsVersion) { final Metrics metrics = new Metrics(); final StreamsMetricsImpl streamsMetrics = - new StreamsMetricsImpl(metrics, "test-client", builtInMetricsVersion, new MockTime()); - final InternalMockProcessorContext context = new InternalMockProcessorContext(streamsMetrics); - final ProcessorNode node = new ProcessorNode<>("name", new NoOpProcessor(), Collections.emptySet()); + new StreamsMetricsImpl(metrics, "test-client", StreamsConfig.METRICS_LATEST, new MockTime()); + final InternalMockProcessorContext context = new InternalMockProcessorContext<>(streamsMetrics); + final ProcessorNode node = new ProcessorNode<>("name", new NoOpProcessor(), Collections.emptySet()); node.init(context); final String threadId = Thread.currentThread().getName(); final String[] latencyOperations = {"process", "punctuate", "create", "destroy"}; final String groupName = "stream-processor-node-metrics"; final Map metricTags = new LinkedHashMap<>(); - final String threadIdTagKey = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? "client-id" : "thread-id"; + final String threadIdTagKey = "client-id"; metricTags.put("processor-node-id", node.name()); metricTags.put("task-id", context.taskId().toString()); metricTags.put(threadIdTagKey, threadId); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - for (final String opName : latencyOperations) { - assertTrue(StreamsTestUtils.containsMetric(metrics, opName + "-latency-avg", groupName, metricTags)); - assertTrue(StreamsTestUtils.containsMetric(metrics, opName + "-latency-max", groupName, metricTags)); - assertTrue(StreamsTestUtils.containsMetric(metrics, opName + "-rate", groupName, metricTags)); - assertTrue(StreamsTestUtils.containsMetric(metrics, opName + "-total", groupName, metricTags)); - } - - // test parent sensors - metricTags.put("processor-node-id", ROLLUP_VALUE); - for (final String opName : latencyOperations) { - assertTrue(StreamsTestUtils.containsMetric(metrics, opName + "-latency-avg", groupName, metricTags)); - assertTrue(StreamsTestUtils.containsMetric(metrics, opName + "-latency-max", groupName, metricTags)); - assertTrue(StreamsTestUtils.containsMetric(metrics, opName + "-rate", groupName, metricTags)); - assertTrue(StreamsTestUtils.containsMetric(metrics, opName + "-total", groupName, metricTags)); - } - } else { - for (final String opName : latencyOperations) { - assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-latency-avg", groupName, metricTags)); - assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-latency-max", groupName, metricTags)); - assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-rate", groupName, metricTags)); - assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-total", groupName, metricTags)); - } - - // test parent sensors - metricTags.put("processor-node-id", ROLLUP_VALUE); - for (final String opName : latencyOperations) { - assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-latency-avg", groupName, metricTags)); - assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-latency-max", groupName, metricTags)); - assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-rate", groupName, metricTags)); - assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-total", groupName, metricTags)); - } + for (final String opName : latencyOperations) { + assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-latency-avg", groupName, metricTags)); + assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-latency-max", groupName, metricTags)); + assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-rate", groupName, metricTags)); + assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-total", groupName, metricTags)); + } + + // test parent sensors + metricTags.put("processor-node-id", ROLLUP_VALUE); + for (final String opName : latencyOperations) { + assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-latency-avg", groupName, metricTags)); + assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-latency-max", groupName, metricTags)); + assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-rate", groupName, metricTags)); + assertFalse(StreamsTestUtils.containsMetric(metrics, opName + "-total", groupName, metricTags)); } } @@ -196,8 +168,8 @@ public void testTopologyLevelClassCastExceptionDirect() { final Metrics metrics = new Metrics(); final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, "test-client", StreamsConfig.METRICS_LATEST, new MockTime()); - final InternalMockProcessorContext context = new InternalMockProcessorContext(streamsMetrics); - final ProcessorNode node = new ProcessorNode<>("name", new ClassCastProcessor(), Collections.emptySet()); + final InternalMockProcessorContext context = new InternalMockProcessorContext<>(streamsMetrics); + final ProcessorNode node = new ProcessorNode<>("name", new ClassCastProcessor(), Collections.emptySet()); node.init(context); final StreamsException se = assertThrows( StreamsException.class, diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorStateManagerTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorStateManagerTest.java index aa5ac9b2ac127..9f8d6bd645e12 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorStateManagerTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorStateManagerTest.java @@ -85,15 +85,16 @@ public class ProcessorStateManagerTest { private final String applicationId = "test-application"; + private final TaskId taskId = new TaskId(0, 1, "My-Topology"); private final String persistentStoreName = "persistentStore"; private final String persistentStoreTwoName = "persistentStore2"; private final String nonPersistentStoreName = "nonPersistentStore"; private final String persistentStoreTopicName = - ProcessorStateManager.storeChangelogTopic(applicationId, persistentStoreName); + ProcessorStateManager.storeChangelogTopic(applicationId, persistentStoreName, taskId.namedTopology()); private final String persistentStoreTwoTopicName = - ProcessorStateManager.storeChangelogTopic(applicationId, persistentStoreTwoName); + ProcessorStateManager.storeChangelogTopic(applicationId, persistentStoreTwoName, taskId.namedTopology()); private final String nonPersistentStoreTopicName = - ProcessorStateManager.storeChangelogTopic(applicationId, nonPersistentStoreName); + ProcessorStateManager.storeChangelogTopic(applicationId, nonPersistentStoreName, taskId.namedTopology()); private final MockKeyValueStore persistentStore = new MockKeyValueStore(persistentStoreName, true); private final MockKeyValueStore persistentStoreTwo = new MockKeyValueStore(persistentStoreTwoName, true); private final MockKeyValueStore nonPersistentStore = new MockKeyValueStore(nonPersistentStoreName, false); @@ -101,7 +102,6 @@ public class ProcessorStateManagerTest { private final TopicPartition persistentStoreTwoPartition = new TopicPartition(persistentStoreTwoTopicName, 1); private final TopicPartition nonPersistentStorePartition = new TopicPartition(nonPersistentStoreTopicName, 1); private final TopicPartition irrelevantPartition = new TopicPartition("other-topic", 1); - private final TaskId taskId = new TaskId(0, 1); private final Integer key = 1; private final String value = "the-value"; private final byte[] keyBytes = new byte[] {0x0, 0x0, 0x0, 0x1}; @@ -134,7 +134,7 @@ public void setup() { put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "dummy:1234"); put(StreamsConfig.STATE_DIR_CONFIG, baseDir.getPath()); } - }), new MockTime(), true); + }), new MockTime(), true, true); checkpointFile = new File(stateDirectory.getOrCreateDirectoryForTask(taskId), CHECKPOINT_FILE_NAME); checkpoint = new OffsetCheckpoint(checkpointFile); @@ -155,11 +155,23 @@ public void shouldReturnDefaultChangelogTopicName() { final String storeName = "store"; assertThat( - ProcessorStateManager.storeChangelogTopic(applicationId, storeName), + ProcessorStateManager.storeChangelogTopic(applicationId, storeName, null), is(applicationId + "-" + storeName + "-changelog") ); } + @Test + public void shouldReturnDefaultChangelogTopicNameWithNamedTopology() { + final String applicationId = "appId"; + final String namedTopology = "namedTopology"; + final String storeName = "store"; + + assertThat( + ProcessorStateManager.storeChangelogTopic(applicationId, storeName, namedTopology), + is(applicationId + "-" + namedTopology + "-" + storeName + "-changelog") + ); + } + @Test public void shouldReturnBaseDir() { final ProcessorStateManager stateMgr = getStateManager(Task.TaskType.ACTIVE); diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorTopologyFactories.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorTopologyFactories.java index b4cef8ad445bf..57e4490d73bd6 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorTopologyFactories.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/ProcessorTopologyFactories.java @@ -27,7 +27,7 @@ private ProcessorTopologyFactories() {} public static ProcessorTopology with(final List> processorNodes, - final Map> sourcesByTopic, + final Map> sourcesByTopic, final List stateStoresByName, final Map storeToChangelogTopic) { return new ProcessorTopology(processorNodes, diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/RecordDeserializerTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/RecordDeserializerTest.java index 18d17aeddadeb..448ceaf67014a 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/RecordDeserializerTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/RecordDeserializerTest.java @@ -68,7 +68,7 @@ public void shouldReturnConsumerRecordWithDeserializedValueWhenNoExceptions() { assertEquals(rawRecord.headers(), record.headers()); } - static class TheSourceNode extends SourceNode { + static class TheSourceNode extends SourceNode { private final boolean keyThrowsException; private final boolean valueThrowsException; private final Object key; diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/RecordQueueTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/RecordQueueTest.java index 142e85a30589a..d23311b7cfc78 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/RecordQueueTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/RecordQueueTest.java @@ -62,11 +62,12 @@ public class RecordQueueTest { private final Deserializer intDeserializer = new IntegerDeserializer(); private final TimestampExtractor timestampExtractor = new MockTimestampExtractor(); - final InternalMockProcessorContext context = new InternalMockProcessorContext( + @SuppressWarnings("rawtypes") + final InternalMockProcessorContext context = new InternalMockProcessorContext<>( StateSerdes.withBuiltinTypes("anyName", Bytes.class, Bytes.class), new MockRecordCollector() ); - private final MockSourceNode mockSourceNodeWithMetrics + private final MockSourceNode mockSourceNodeWithMetrics = new MockSourceNode<>(intDeserializer, intDeserializer); private final RecordQueue queue = new RecordQueue( new TopicPartition("topic", 1), @@ -86,6 +87,7 @@ public class RecordQueueTest { private final byte[] recordValue = intSerializer.serialize(null, 10); private final byte[] recordKey = intSerializer.serialize(null, 1); + @SuppressWarnings("unchecked") @Before public void before() { mockSourceNodeWithMetrics.init(context); diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/RepartitionTopicsTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/RepartitionTopicsTest.java index 30a4641e3d2a9..88b70e0c32d55 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/RepartitionTopicsTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/RepartitionTopicsTest.java @@ -20,10 +20,13 @@ import org.apache.kafka.common.Node; import org.apache.kafka.common.PartitionInfo; import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.errors.MissingSourceTopicException; import org.apache.kafka.streams.errors.TaskAssignmentException; import org.apache.kafka.streams.processor.internals.InternalTopologyBuilder.TopicsInfo; import org.apache.kafka.streams.processor.internals.assignment.CopartitionedTopicsEnforcer; +import org.apache.kafka.streams.processor.internals.testutil.DummyStreamsConfig; + import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; @@ -90,6 +93,7 @@ public class RepartitionTopicsTest { mkMap(mkEntry(REPARTITION_TOPIC_NAME1, REPARTITION_TOPIC_CONFIG1)), Collections.emptyMap() ); + final StreamsConfig config = new DummyStreamsConfig(); final InternalTopologyBuilder internalTopologyBuilder = mock(InternalTopologyBuilder.class); final InternalTopicManager internalTopicManager = mock(InternalTopicManager.class); @@ -98,6 +102,7 @@ public class RepartitionTopicsTest { @Test public void shouldSetupRepartitionTopics() { + expect(internalTopologyBuilder.hasNamedTopology()).andStubReturn(false); expect(internalTopologyBuilder.topicGroups()) .andReturn(mkMap(mkEntry(SUBTOPOLOGY_0, TOPICS_INFO1), mkEntry(SUBTOPOLOGY_1, TOPICS_INFO2))); final Set coPartitionGroup1 = mkSet(SOURCE_TOPIC_NAME1, SOURCE_TOPIC_NAME2); @@ -115,7 +120,7 @@ public void shouldSetupRepartitionTopics() { setupCluster(); replay(internalTopicManager, internalTopologyBuilder, clusterMetadata); final RepartitionTopics repartitionTopics = new RepartitionTopics( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), internalTopicManager, copartitionedTopicsEnforcer, clusterMetadata, @@ -137,6 +142,7 @@ public void shouldSetupRepartitionTopics() { @Test public void shouldThrowMissingSourceTopicException() { + expect(internalTopologyBuilder.hasNamedTopology()).andStubReturn(false); expect(internalTopologyBuilder.topicGroups()) .andReturn(mkMap(mkEntry(SUBTOPOLOGY_0, TOPICS_INFO1), mkEntry(SUBTOPOLOGY_1, TOPICS_INFO2))); expect(internalTopologyBuilder.copartitionGroups()).andReturn(Collections.emptyList()); @@ -149,7 +155,7 @@ public void shouldThrowMissingSourceTopicException() { setupClusterWithMissingTopics(mkSet(SOURCE_TOPIC_NAME1)); replay(internalTopicManager, internalTopologyBuilder, clusterMetadata); final RepartitionTopics repartitionTopics = new RepartitionTopics( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), internalTopicManager, copartitionedTopicsEnforcer, clusterMetadata, @@ -163,6 +169,7 @@ public void shouldThrowMissingSourceTopicException() { public void shouldThrowTaskAssignmentExceptionIfPartitionCountCannotBeComputedForAllRepartitionTopics() { final RepartitionTopicConfig repartitionTopicConfigWithoutPartitionCount = new RepartitionTopicConfig(REPARTITION_WITHOUT_PARTITION_COUNT, TOPIC_CONFIG5); + expect(internalTopologyBuilder.hasNamedTopology()).andStubReturn(false); expect(internalTopologyBuilder.topicGroups()) .andReturn(mkMap( mkEntry(SUBTOPOLOGY_0, TOPICS_INFO1), @@ -178,7 +185,7 @@ public void shouldThrowTaskAssignmentExceptionIfPartitionCountCannotBeComputedFo setupCluster(); replay(internalTopicManager, internalTopologyBuilder, clusterMetadata); final RepartitionTopics repartitionTopics = new RepartitionTopics( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), internalTopicManager, copartitionedTopicsEnforcer, clusterMetadata, @@ -201,6 +208,7 @@ public void shouldThrowTaskAssignmentExceptionIfSourceTopicHasNoPartitionCount() ), Collections.emptyMap() ); + expect(internalTopologyBuilder.hasNamedTopology()).andStubReturn(false); expect(internalTopologyBuilder.topicGroups()) .andReturn(mkMap( mkEntry(SUBTOPOLOGY_0, topicsInfo), @@ -216,7 +224,7 @@ public void shouldThrowTaskAssignmentExceptionIfSourceTopicHasNoPartitionCount() setupClusterWithMissingPartitionCounts(mkSet(SOURCE_TOPIC_NAME1)); replay(internalTopicManager, internalTopologyBuilder, clusterMetadata); final RepartitionTopics repartitionTopics = new RepartitionTopics( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), internalTopicManager, copartitionedTopicsEnforcer, clusterMetadata, @@ -244,6 +252,7 @@ public void shouldSetRepartitionTopicPartitionCountFromUpstreamExternalSourceTop ), Collections.emptyMap() ); + expect(internalTopologyBuilder.hasNamedTopology()).andStubReturn(false); expect(internalTopologyBuilder.topicGroups()) .andReturn(mkMap( mkEntry(SUBTOPOLOGY_0, topicsInfo), @@ -261,7 +270,7 @@ public void shouldSetRepartitionTopicPartitionCountFromUpstreamExternalSourceTop setupCluster(); replay(internalTopicManager, internalTopologyBuilder, clusterMetadata); final RepartitionTopics repartitionTopics = new RepartitionTopics( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), internalTopicManager, copartitionedTopicsEnforcer, clusterMetadata, @@ -298,6 +307,7 @@ public void shouldSetRepartitionTopicPartitionCountFromUpstreamInternalRepartiti ), Collections.emptyMap() ); + expect(internalTopologyBuilder.hasNamedTopology()).andStubReturn(false); expect(internalTopologyBuilder.topicGroups()) .andReturn(mkMap( mkEntry(SUBTOPOLOGY_0, topicsInfo), @@ -315,7 +325,7 @@ public void shouldSetRepartitionTopicPartitionCountFromUpstreamInternalRepartiti setupCluster(); replay(internalTopicManager, internalTopologyBuilder, clusterMetadata); final RepartitionTopics repartitionTopics = new RepartitionTopics( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), internalTopicManager, copartitionedTopicsEnforcer, clusterMetadata, @@ -347,6 +357,7 @@ public void shouldNotSetupRepartitionTopicsWhenTopologyDoesNotContainAnyRepartit Collections.emptyMap(), Collections.emptyMap() ); + expect(internalTopologyBuilder.hasNamedTopology()).andStubReturn(false); expect(internalTopologyBuilder.topicGroups()) .andReturn(mkMap(mkEntry(SUBTOPOLOGY_0, topicsInfo))); expect(internalTopologyBuilder.copartitionGroups()).andReturn(Collections.emptySet()); @@ -354,7 +365,7 @@ public void shouldNotSetupRepartitionTopicsWhenTopologyDoesNotContainAnyRepartit setupCluster(); replay(internalTopicManager, internalTopologyBuilder, clusterMetadata); final RepartitionTopics repartitionTopics = new RepartitionTopics( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), internalTopicManager, copartitionedTopicsEnforcer, clusterMetadata, diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/SinkNodeTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/SinkNodeTest.java index 30c7b1b3ee337..7e7f7b824b5c2 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/SinkNodeTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/SinkNodeTest.java @@ -33,13 +33,13 @@ public class SinkNodeTest { private final StateSerdes anyStateSerde = StateSerdes.withBuiltinTypes("anyName", Bytes.class, Bytes.class); private final Serializer anySerializer = Serdes.ByteArray().serializer(); private final RecordCollector recordCollector = new MockRecordCollector(); - private final InternalMockProcessorContext context = new InternalMockProcessorContext(anyStateSerde, recordCollector); - private final SinkNode sink = new SinkNode<>("anyNodeName", + private final InternalMockProcessorContext context = new InternalMockProcessorContext<>(anyStateSerde, recordCollector); + private final SinkNode sink = new SinkNode<>("anyNodeName", new StaticTopicNameExtractor<>("any-output-topic"), anySerializer, anySerializer, null); // Used to verify that the correct exceptions are thrown if the compiler checks are bypassed - @SuppressWarnings("unchecked") - private final SinkNode illTypedSink = (SinkNode) sink; + @SuppressWarnings({"unchecked", "rawtypes"}) + private final SinkNode illTypedSink = (SinkNode) sink; @Before public void before() { diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/SourceNodeTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/SourceNodeTest.java index 92e47194becbe..03f22a3a917d6 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/SourceNodeTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/SourceNodeTest.java @@ -44,7 +44,7 @@ public class SourceNodeTest { @Test public void shouldProvideTopicHeadersAndDataToKeyDeserializer() { - final SourceNode sourceNode = new MockSourceNode<>(new TheDeserializer(), new TheDeserializer()); + final SourceNode sourceNode = new MockSourceNode<>(new TheDeserializer(), new TheDeserializer()); final RecordHeaders headers = new RecordHeaders(); final String deserializeKey = sourceNode.deserializeKey("topic", headers, "data".getBytes(StandardCharsets.UTF_8)); assertThat(deserializeKey, is("topic" + headers + "data")); @@ -52,7 +52,7 @@ public void shouldProvideTopicHeadersAndDataToKeyDeserializer() { @Test public void shouldProvideTopicHeadersAndDataToValueDeserializer() { - final SourceNode sourceNode = new MockSourceNode<>(new TheDeserializer(), new TheDeserializer()); + final SourceNode sourceNode = new MockSourceNode<>(new TheDeserializer(), new TheDeserializer()); final RecordHeaders headers = new RecordHeaders(); final String deserializedValue = sourceNode.deserializeValue("topic", headers, "data".getBytes(StandardCharsets.UTF_8)); assertThat(deserializedValue, is("topic" + headers + "data")); @@ -71,61 +71,39 @@ public String deserialize(final String topic, final byte[] data) { } @Test - public void shouldExposeProcessMetricsWithBuiltInMetricsVersionLatest() { - shouldExposeProcessMetrics(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldExposeProcessWithBuiltInMetricsVersion0100To24() { - shouldExposeProcessMetrics(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldExposeProcessMetrics(final String builtInMetricsVersion) { + public void shouldExposeProcessMetrics() { final Metrics metrics = new Metrics(); final StreamsMetricsImpl streamsMetrics = - new StreamsMetricsImpl(metrics, "test-client", builtInMetricsVersion, new MockTime()); - final InternalMockProcessorContext context = new InternalMockProcessorContext(streamsMetrics); - final SourceNode node = + new StreamsMetricsImpl(metrics, "test-client", StreamsConfig.METRICS_LATEST, new MockTime()); + final InternalMockProcessorContext context = new InternalMockProcessorContext<>(streamsMetrics); + final SourceNode node = new SourceNode<>(context.currentNode().name(), new TheDeserializer(), new TheDeserializer()); node.init(context); final String threadId = Thread.currentThread().getName(); final String groupName = "stream-processor-node-metrics"; - final String threadIdTagKey = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? "client-id" : "thread-id"; final Map metricTags = mkMap( - mkEntry(threadIdTagKey, threadId), + mkEntry("thread-id", threadId), mkEntry("task-id", context.taskId().toString()), mkEntry("processor-node-id", node.name()) ); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertTrue(StreamsTestUtils.containsMetric(metrics, "forward-rate", groupName, metricTags)); - assertTrue(StreamsTestUtils.containsMetric(metrics, "forward-total", groupName, metricTags)); - - // test parent sensors - metricTags.put("processor-node-id", StreamsMetricsImpl.ROLLUP_VALUE); - assertTrue(StreamsTestUtils.containsMetric(metrics, "forward-rate", groupName, metricTags)); - assertTrue(StreamsTestUtils.containsMetric(metrics, "forward-total", groupName, metricTags)); - - } else { - assertTrue(StreamsTestUtils.containsMetric(metrics, "process-rate", groupName, metricTags)); - assertTrue(StreamsTestUtils.containsMetric(metrics, "process-total", groupName, metricTags)); - - // test parent sensors - final String parentGroupName = "stream-task-metrics"; - metricTags.remove("processor-node-id"); - assertTrue(StreamsTestUtils.containsMetric(metrics, "process-rate", parentGroupName, metricTags)); - assertTrue(StreamsTestUtils.containsMetric(metrics, "process-total", parentGroupName, metricTags)); - - final String sensorNamePrefix = "internal." + threadId + ".task." + context.taskId().toString(); - final Sensor processSensor = - metrics.getSensor(sensorNamePrefix + ".node." + context.currentNode().name() + ".s.process"); - final SensorAccessor sensorAccessor = new SensorAccessor(processSensor); - assertThat( - sensorAccessor.parents().stream().map(Sensor::name).collect(Collectors.toList()), - contains(sensorNamePrefix + ".s.process") - ); - } + assertTrue(StreamsTestUtils.containsMetric(metrics, "process-rate", groupName, metricTags)); + assertTrue(StreamsTestUtils.containsMetric(metrics, "process-total", groupName, metricTags)); + + // test parent sensors + final String parentGroupName = "stream-task-metrics"; + metricTags.remove("processor-node-id"); + assertTrue(StreamsTestUtils.containsMetric(metrics, "process-rate", parentGroupName, metricTags)); + assertTrue(StreamsTestUtils.containsMetric(metrics, "process-total", parentGroupName, metricTags)); + + final String sensorNamePrefix = "internal." + threadId + ".task." + context.taskId().toString(); + final Sensor processSensor = + metrics.getSensor(sensorNamePrefix + ".node." + context.currentNode().name() + ".s.process"); + final SensorAccessor sensorAccessor = new SensorAccessor(processSensor); + assertThat( + sensorAccessor.parents().stream().map(Sensor::name).collect(Collectors.toList()), + contains(sensorNamePrefix + ".s.process") + ); } } diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StandbyTaskTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StandbyTaskTest.java index 60a95d4834427..82231bccbab3c 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StandbyTaskTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StandbyTaskTest.java @@ -77,13 +77,13 @@ public class StandbyTaskTest { private final String threadName = "threadName"; private final String threadId = Thread.currentThread().getName(); - private final TaskId taskId = new TaskId(0, 0); + private final TaskId taskId = new TaskId(0, 0, "My-Topology"); private final String storeName1 = "store1"; private final String storeName2 = "store2"; private final String applicationId = "test-application"; - private final String storeChangelogTopicName1 = ProcessorStateManager.storeChangelogTopic(applicationId, storeName1); - private final String storeChangelogTopicName2 = ProcessorStateManager.storeChangelogTopic(applicationId, storeName2); + private final String storeChangelogTopicName1 = ProcessorStateManager.storeChangelogTopic(applicationId, storeName1, taskId.namedTopology()); + private final String storeChangelogTopicName2 = ProcessorStateManager.storeChangelogTopic(applicationId, storeName2, taskId.namedTopology()); private final TopicPartition partition = new TopicPartition(storeChangelogTopicName1, 0); private final MockKeyValueStore store1 = (MockKeyValueStore) new MockKeyValueStoreBuilder(storeName1, false).build(); @@ -138,7 +138,7 @@ public void setup() throws Exception { )); baseDir = TestUtils.tempDirectory(); config = createConfig(baseDir); - stateDirectory = new StateDirectory(config, new MockTime(), true); + stateDirectory = new StateDirectory(config, new MockTime(), true, true); } @After diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StateDirectoryTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StateDirectoryTest.java index 7b68b8a731fad..984ba1cb9200e 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StateDirectoryTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StateDirectoryTest.java @@ -361,7 +361,9 @@ public void shouldReturnEmptyArrayIfListFilesReturnsNull() throws IOException { put(StreamsConfig.STATE_DIR_CONFIG, stateDir.getPath()); } }), - time, true); + time, + true, + false); appDir = new File(stateDir, applicationId); // make sure the File#listFiles returns null and StateDirectory#listAllTaskDirectories is able to handle null @@ -402,7 +404,9 @@ public void shouldCreateDirectoriesIfParentDoesntExist() { put(StreamsConfig.STATE_DIR_CONFIG, stateDir.getPath()); } }), - time, true); + time, + true, + false); final File taskDir = stateDirectory.getOrCreateDirectoryForTask(new TaskId(0, 0)); assertTrue(stateDir.exists()); assertTrue(taskDir.exists()); diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamTaskTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamTaskTest.java index 9763c4f0d9113..f145e6dace735 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamTaskTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamTaskTest.java @@ -105,7 +105,6 @@ import static org.apache.kafka.streams.processor.internals.Task.State.RUNNING; import static org.apache.kafka.streams.processor.internals.Task.State.SUSPENDED; import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.THREAD_ID_TAG; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.THREAD_ID_TAG_0100_TO_24; import static org.apache.kafka.test.StreamsTestUtils.getMetricByNameFilterByTags; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.nullValue; @@ -137,9 +136,9 @@ public class StreamTaskTest { private final Serializer intSerializer = Serdes.Integer().serializer(); private final Deserializer intDeserializer = Serdes.Integer().deserializer(); - private final MockSourceNode source1 = new MockSourceNode<>(intDeserializer, intDeserializer); - private final MockSourceNode source2 = new MockSourceNode<>(intDeserializer, intDeserializer); - private final MockSourceNode source3 = new MockSourceNode(intDeserializer, intDeserializer) { + private final MockSourceNode source1 = new MockSourceNode<>(intDeserializer, intDeserializer); + private final MockSourceNode source2 = new MockSourceNode<>(intDeserializer, intDeserializer); + private final MockSourceNode source3 = new MockSourceNode(intDeserializer, intDeserializer) { @Override public void process(final Record record) { throw new RuntimeException("KABOOM!"); @@ -150,7 +149,7 @@ public void close() { throw new RuntimeException("KABOOM!"); } }; - private final MockSourceNode timeoutSource = new MockSourceNode(intDeserializer, intDeserializer) { + private final MockSourceNode timeoutSource = new MockSourceNode(intDeserializer, intDeserializer) { @Override public void process(final Record record) { throw new TimeoutException("Kaboom!"); @@ -192,7 +191,7 @@ public void punctuate(final long timestamp) { }; private static ProcessorTopology withRepartitionTopics(final List> processorNodes, - final Map> sourcesByTopic, + final Map> sourcesByTopic, final Set repartitionTopics) { return new ProcessorTopology(processorNodes, sourcesByTopic, @@ -204,7 +203,7 @@ private static ProcessorTopology withRepartitionTopics(final List> processorNodes, - final Map> sourcesByTopic) { + final Map> sourcesByTopic) { return new ProcessorTopology(processorNodes, sourcesByTopic, emptyMap(), @@ -247,7 +246,7 @@ public void setup() { consumer.assign(asList(partition1, partition2)); consumer.updateBeginningOffsets(mkMap(mkEntry(partition1, 0L), mkEntry(partition2, 0L))); - stateDirectory = new StateDirectory(createConfig("100"), new MockTime(), true); + stateDirectory = new StateDirectory(createConfig("100"), new MockTime(), true, false); } @After @@ -287,7 +286,7 @@ public void shouldNotAttemptToLockIfNoStores() { stateDirectory = EasyMock.createNiceMock(StateDirectory.class); EasyMock.replay(stateDirectory); - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); @@ -331,7 +330,7 @@ public void shouldAttemptToDeleteStateDirectoryWhenCloseDirtyAndEosEnabled() thr @Test public void shouldResetOffsetsToLastCommittedForSpecifiedPartitions() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.addPartitionsForOffsetReset(Collections.singleton(partition1)); consumer.seek(partition1, 5L); @@ -355,7 +354,7 @@ public void shouldResetOffsetsToLastCommittedForSpecifiedPartitions() { @Test public void shouldAutoOffsetResetIfNoCommittedOffsetFound() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.addPartitionsForOffsetReset(Collections.singleton(partition1)); final AtomicReference shouldNotSeek = new AtomicReference<>(); @@ -400,7 +399,7 @@ public void shouldReadCommittedStreamTimeOnInitialize() { consumer.commitSync(partitions.stream() .collect(Collectors.toMap(Function.identity(), tp -> new OffsetAndMetadata(0L, encodeTimestamp(10L))))); - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); assertEquals(RecordQueue.UNKNOWN, task.streamTime()); @@ -447,7 +446,7 @@ public void shouldTransitToRestoringThenRunningAfterCreation() throws IOExceptio @Test public void shouldProcessInOrder() { - task = createStatelessTask(createConfig(), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig()); task.addRecords(partition1, asList( getConsumerRecordWithOffsetAsTimestamp(partition1, 10, 101), @@ -574,7 +573,7 @@ public void shouldNotProcessRecordsAfterPrepareCommitWhenEosV2Enabled() { public void shouldRecordBufferedRecords() { task = createSingleSourceStateless(createConfig(AT_LEAST_ONCE, "0"), StreamsConfig.METRICS_LATEST); - final KafkaMetric metric = getMetric("active-buffer", "%s-count", task.id().toString(), StreamsConfig.METRICS_LATEST); + final KafkaMetric metric = getMetric("active-buffer", "%s-count", task.id().toString()); assertThat(metric.metricValue(), equalTo(0.0)); @@ -594,9 +593,9 @@ public void shouldRecordBufferedRecords() { @Test public void shouldRecordProcessRatio() { - task = createStatelessTask(createConfig(), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig()); - final KafkaMetric metric = getMetric("active-process", "%s-ratio", task.id().toString(), StreamsConfig.METRICS_LATEST); + final KafkaMetric metric = getMetric("active-process", "%s-ratio", task.id().toString()); assertThat(metric.metricValue(), equalTo(0.0)); @@ -622,11 +621,11 @@ public void shouldRecordE2ELatencyOnSourceNodeAndTerminalNodes() { metrics = new Metrics(new MetricConfig().recordLevel(Sensor.RecordingLevel.INFO), time); // Create a processor that only forwards even keys to test the metrics at the source and terminal nodes - final MockSourceNode evenKeyForwardingSourceNode = new MockSourceNode(intDeserializer, intDeserializer) { - InternalProcessorContext context; + final MockSourceNode evenKeyForwardingSourceNode = new MockSourceNode(intDeserializer, intDeserializer) { + InternalProcessorContext context; @Override - public void init(final InternalProcessorContext context) { + public void init(final InternalProcessorContext context) { this.context = context; super.init(context); } @@ -748,119 +747,78 @@ public void shouldThrowTaskCorruptedExceptionOnTimeoutExceptionIfEosEnabled() { } @Test - public void shouldConstructMetricsWithBuiltInMetricsVersion0100To24() { - testMetrics(StreamsConfig.METRICS_0100_TO_24); - } - - @Test - public void shouldConstructMetricsWithBuiltInMetricsVersionLatest() { - testMetrics(StreamsConfig.METRICS_LATEST); - } - - private void testMetrics(final String builtInMetricsVersion) { - task = createStatelessTask(createConfig("100"), builtInMetricsVersion); + public void testMetrics() { + task = createStatelessTask(createConfig("100")); assertNotNull(getMetric( "enforced-processing", "%s-rate", - task.id().toString(), - builtInMetricsVersion + task.id().toString() )); assertNotNull(getMetric( "enforced-processing", "%s-total", - task.id().toString(), - builtInMetricsVersion + task.id().toString() )); assertNotNull(getMetric( "record-lateness", "%s-avg", - task.id().toString(), - builtInMetricsVersion + task.id().toString() )); assertNotNull(getMetric( "record-lateness", "%s-max", - task.id().toString(), - builtInMetricsVersion + task.id().toString() )); assertNotNull(getMetric( "active-process", "%s-ratio", - task.id().toString(), - builtInMetricsVersion + task.id().toString() )); assertNotNull(getMetric( "active-buffer", "%s-count", - task.id().toString(), - builtInMetricsVersion + task.id().toString() )); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - testMetricsForBuiltInMetricsVersion0100To24(); - } else { - testMetricsForBuiltInMetricsVersionLatest(); - } + testMetricsForBuiltInMetricsVersionLatest(); final JmxReporter reporter = new JmxReporter(); final MetricsContext metricsContext = new KafkaMetricsContext("kafka.streams"); reporter.contextChange(metricsContext); metrics.addReporter(reporter); - final String threadIdTag = - StreamsConfig.METRICS_LATEST.equals(builtInMetricsVersion) ? THREAD_ID_TAG : THREAD_ID_TAG_0100_TO_24; + final String threadIdTag = THREAD_ID_TAG; assertTrue(reporter.containsMbean(String.format( "kafka.streams:type=stream-task-metrics,%s=%s,task-id=%s", threadIdTag, threadId, task.id() ))); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertTrue(reporter.containsMbean(String.format( - "kafka.streams:type=stream-task-metrics,%s=%s,task-id=all", - threadIdTag, - threadId - ))); - } } private void testMetricsForBuiltInMetricsVersionLatest() { final String builtInMetricsVersion = StreamsConfig.METRICS_LATEST; - assertNull(getMetric("commit", "%s-latency-avg", "all", builtInMetricsVersion)); - assertNull(getMetric("commit", "%s-latency-max", "all", builtInMetricsVersion)); - assertNull(getMetric("commit", "%s-rate", "all", builtInMetricsVersion)); - assertNull(getMetric("commit", "%s-total", "all", builtInMetricsVersion)); + assertNull(getMetric("commit", "%s-latency-avg", "all")); + assertNull(getMetric("commit", "%s-latency-max", "all")); + assertNull(getMetric("commit", "%s-rate", "all")); + assertNull(getMetric("commit", "%s-total", "all")); - assertNotNull(getMetric("process", "%s-latency-max", task.id().toString(), builtInMetricsVersion)); - assertNotNull(getMetric("process", "%s-latency-avg", task.id().toString(), builtInMetricsVersion)); + assertNotNull(getMetric("process", "%s-latency-max", task.id().toString())); + assertNotNull(getMetric("process", "%s-latency-avg", task.id().toString())); - assertNotNull(getMetric("punctuate", "%s-latency-avg", task.id().toString(), builtInMetricsVersion)); - assertNotNull(getMetric("punctuate", "%s-latency-max", task.id().toString(), builtInMetricsVersion)); - assertNotNull(getMetric("punctuate", "%s-rate", task.id().toString(), builtInMetricsVersion)); - assertNotNull(getMetric("punctuate", "%s-total", task.id().toString(), builtInMetricsVersion)); - } - - private void testMetricsForBuiltInMetricsVersion0100To24() { - final String builtInMetricsVersion = StreamsConfig.METRICS_0100_TO_24; - assertNotNull(getMetric("commit", "%s-rate", "all", builtInMetricsVersion)); - - assertNull(getMetric("process", "%s-latency-avg", task.id().toString(), builtInMetricsVersion)); - assertNull(getMetric("process", "%s-latency-max", task.id().toString(), builtInMetricsVersion)); - - assertNull(getMetric("punctuate", "%s-latency-avg", task.id().toString(), builtInMetricsVersion)); - assertNull(getMetric("punctuate", "%s-latency-max", task.id().toString(), builtInMetricsVersion)); - assertNull(getMetric("punctuate", "%s-rate", task.id().toString(), builtInMetricsVersion)); - assertNull(getMetric("punctuate", "%s-total", task.id().toString(), builtInMetricsVersion)); + assertNotNull(getMetric("punctuate", "%s-latency-avg", task.id().toString())); + assertNotNull(getMetric("punctuate", "%s-latency-max", task.id().toString())); + assertNotNull(getMetric("punctuate", "%s-rate", task.id().toString())); + assertNotNull(getMetric("punctuate", "%s-total", task.id().toString())); } private KafkaMetric getMetric(final String operation, final String nameFormat, - final String taskId, - final String builtInMetricsVersion) { + final String taskId) { final String descriptionIsNotVerified = ""; return metrics.metrics().get(metrics.metricName( String.format(nameFormat, operation), @@ -868,11 +826,7 @@ private KafkaMetric getMetric(final String operation, descriptionIsNotVerified, mkMap( mkEntry("task-id", taskId), - mkEntry( - StreamsConfig.METRICS_LATEST.equals(builtInMetricsVersion) ? THREAD_ID_TAG - : THREAD_ID_TAG_0100_TO_24, - Thread.currentThread().getName() - ) + mkEntry(THREAD_ID_TAG, Thread.currentThread().getName()) ) )); } @@ -890,10 +844,7 @@ private Metric getProcessorMetric(final String operation, mkMap( mkEntry("task-id", taskId), mkEntry("processor-node-id", processorNodeId), - mkEntry( - StreamsConfig.METRICS_LATEST.equals(builtInMetricsVersion) ? THREAD_ID_TAG - : THREAD_ID_TAG_0100_TO_24, - Thread.currentThread().getName() + mkEntry(THREAD_ID_TAG, Thread.currentThread().getName() ) ) ); @@ -901,7 +852,7 @@ private Metric getProcessorMetric(final String operation, @Test public void shouldPauseAndResumeBasedOnBufferedRecords() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.addRecords(partition1, asList( getConsumerRecordWithOffsetAsTimestamp(partition1, 10), @@ -955,7 +906,7 @@ public void shouldPauseAndResumeBasedOnBufferedRecords() { @Test public void shouldPunctuateOnceStreamTimeAfterGap() { - task = createStatelessTask(createConfig(), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig()); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); @@ -1040,7 +991,7 @@ public void shouldPunctuateOnceStreamTimeAfterGap() { @Test public void shouldRespectPunctuateCancellationStreamTime() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); @@ -1080,7 +1031,7 @@ public void shouldRespectPunctuateCancellationStreamTime() { @Test public void shouldRespectPunctuateCancellationSystemTime() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); final long now = time.milliseconds(); @@ -1151,7 +1102,7 @@ public void shouldCommitNextOffsetFromQueueIfAvailable() { @Test public void shouldCommitConsumerPositionIfRecordQueueIsEmpty() { - task = createStatelessTask(createConfig(), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig()); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); @@ -1171,7 +1122,7 @@ public void shouldCommitConsumerPositionIfRecordQueueIsEmpty() { @Test public void shouldFailOnCommitIfTaskIsClosed() { - task = createStatelessTask(createConfig(), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig()); task.suspend(); task.transitionTo(Task.State.CLOSED); @@ -1185,7 +1136,7 @@ public void shouldFailOnCommitIfTaskIsClosed() { @Test public void shouldRespectCommitRequested() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); @@ -1195,13 +1146,13 @@ public void shouldRespectCommitRequested() { @Test public void shouldEncodeAndDecodeMetadata() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); assertEquals(DEFAULT_TIMESTAMP, task.decodeTimestamp(encodeTimestamp(DEFAULT_TIMESTAMP))); } @Test public void shouldReturnUnknownTimestampIfUnknownVersion() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); final byte[] emptyMessage = {StreamTask.LATEST_MAGIC_BYTE + 1}; final String encodedString = Base64.getEncoder().encodeToString(emptyMessage); @@ -1210,14 +1161,14 @@ public void shouldReturnUnknownTimestampIfUnknownVersion() { @Test public void shouldReturnUnknownTimestampIfEmptyMessage() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); assertEquals(RecordQueue.UNKNOWN, task.decodeTimestamp("")); } @Test public void shouldBeProcessableIfAllPartitionsBuffered() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); @@ -1241,7 +1192,7 @@ public void shouldBeProcessableIfAllPartitionsBuffered() { @Test public void shouldBeRecordIdlingTimeIfSuspended() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); task.suspend(); @@ -1254,7 +1205,7 @@ public void shouldBeRecordIdlingTimeIfSuspended() { } public void shouldPunctuateSystemTimeWhenIntervalElapsed() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); final long now = time.milliseconds(); @@ -1274,7 +1225,7 @@ public void shouldPunctuateSystemTimeWhenIntervalElapsed() { @Test public void shouldNotPunctuateSystemTimeWhenIntervalNotElapsed() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); assertFalse(task.maybePunctuateSystemTime()); @@ -1285,7 +1236,7 @@ public void shouldNotPunctuateSystemTimeWhenIntervalNotElapsed() { @Test public void shouldPunctuateOnceSystemTimeAfterGap() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); final long now = time.milliseconds(); @@ -1311,7 +1262,7 @@ public void shouldPunctuateOnceSystemTimeAfterGap() { @Test public void shouldWrapKafkaExceptionsWithStreamsExceptionAndAddContextWhenPunctuatingStreamTime() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); @@ -1329,7 +1280,7 @@ public void shouldWrapKafkaExceptionsWithStreamsExceptionAndAddContextWhenPunctu @Test public void shouldWrapKafkaExceptionsWithStreamsExceptionAndAddContextWhenPunctuatingWallClockTimeTime() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); @@ -1347,7 +1298,7 @@ public void shouldWrapKafkaExceptionsWithStreamsExceptionAndAddContextWhenPunctu @Test public void shouldNotShareHeadersBetweenPunctuateIterations() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); @@ -1528,9 +1479,10 @@ public void shouldNotCheckpointOffsetsOnCommitIfEosIsEnabled() { assertFalse(checkpointFile.exists()); } + @SuppressWarnings("unchecked") @Test public void shouldThrowIllegalStateExceptionIfCurrentNodeIsNotNullWhenPunctuateCalled() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); task.processorContext().setCurrentNode(processorStreamTime); @@ -1544,7 +1496,7 @@ public void shouldThrowIllegalStateExceptionIfCurrentNodeIsNotNullWhenPunctuateC @Test public void shouldCallPunctuateOnPassedInProcessorNode() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); task.punctuate(processorStreamTime, 5, PunctuationType.STREAM_TIME, punctuator); @@ -1555,7 +1507,7 @@ public void shouldCallPunctuateOnPassedInProcessorNode() { @Test public void shouldSetProcessorNodeOnContextBackToNullAfterSuccessfulPunctuate() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); task.punctuate(processorStreamTime, 5, PunctuationType.STREAM_TIME, punctuator); @@ -1564,13 +1516,14 @@ public void shouldSetProcessorNodeOnContextBackToNullAfterSuccessfulPunctuate() @Test public void shouldThrowIllegalStateExceptionOnScheduleIfCurrentNodeIsNull() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); assertThrows(IllegalStateException.class, () -> task.schedule(1, PunctuationType.STREAM_TIME, timestamp -> { })); } + @SuppressWarnings("unchecked") @Test public void shouldNotThrowExceptionOnScheduleIfCurrentNodeIsNotNull() { - task = createStatelessTask(createConfig("100"), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig("100")); task.processorContext().setCurrentNode(processorStreamTime); task.schedule(1, PunctuationType.STREAM_TIME, timestamp -> { }); } @@ -1672,7 +1625,7 @@ public Map committed(final Set newPartitions = new HashSet<>(task.inputPartitions()); newPartitions.add(new TopicPartition("newTopic", 0)); @@ -2120,7 +2073,7 @@ public void shouldThrowIfCleanClosingDirtyTask() { @Test public void shouldThrowIfRecyclingDirtyTask() { - task = createStatelessTask(createConfig(), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig()); task.initializeIfNeeded(); task.completeRestoration(noOpResetter -> { }); @@ -2228,7 +2181,7 @@ public void shouldThrowTopologyExceptionIfTaskCreatedForUnknownTopic() { @Test public void shouldInitTaskTimeoutAndEventuallyThrow() { - task = createStatelessTask(createConfig(), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig()); task.maybeInitTaskTimeoutOrThrow(0L, null); task.maybeInitTaskTimeoutOrThrow(Duration.ofMinutes(5).toMillis(), null); @@ -2241,7 +2194,7 @@ public void shouldInitTaskTimeoutAndEventuallyThrow() { @Test public void shouldCLearTaskTimeout() { - task = createStatelessTask(createConfig(), StreamsConfig.METRICS_LATEST); + task = createStatelessTask(createConfig()); task.maybeInitTaskTimeoutOrThrow(0L, null); task.clearTaskTimeout(); @@ -2438,8 +2391,7 @@ private StreamTask createSingleSourceStateless(final StreamsConfig config, ); } - private StreamTask createStatelessTask(final StreamsConfig config, - final String builtInMetricsVersion) { + private StreamTask createStatelessTask(final StreamsConfig config) { final ProcessorTopology topology = withSources( asList(source1, source2, processorStreamTime, processorSystemTime), mkMap(mkEntry(topic1, source1), mkEntry(topic2, source2)) @@ -2469,7 +2421,7 @@ private StreamTask createStatelessTask(final StreamsConfig config, topology, consumer, config, - new StreamsMetricsImpl(metrics, "test", builtInMetricsVersion, time), + new StreamsMetricsImpl(metrics, "test", StreamsConfig.METRICS_LATEST, time), stateDirectory, cache, time, @@ -2480,7 +2432,7 @@ private StreamTask createStatelessTask(final StreamsConfig config, ); } - private StreamTask createStatelessTaskWithForwardingTopology(final SourceNode sourceNode) { + private StreamTask createStatelessTaskWithForwardingTopology(final SourceNode sourceNode) { final ProcessorTopology topology = withSources( asList(sourceNode, processorStreamTime), singletonMap(topic1, sourceNode) diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamThreadTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamThreadTest.java index a1379f8b8a823..bfb03b189fad8 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamThreadTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamThreadTest.java @@ -130,7 +130,6 @@ import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -152,7 +151,7 @@ public class StreamThreadTest { private final StreamsConfig config = new StreamsConfig(configProps(false)); private final ConsumedInternal consumed = new ConsumedInternal<>(); private final ChangelogReader changelogReader = new MockChangelogReader(); - private final StateDirectory stateDirectory = new StateDirectory(config, mockTime, true); + private final StateDirectory stateDirectory = new StateDirectory(config, mockTime, true, false); private final InternalTopologyBuilder internalTopologyBuilder = new InternalTopologyBuilder(); private final InternalStreamsBuilder internalStreamsBuilder = new InternalStreamsBuilder(internalTopologyBuilder); @@ -171,7 +170,7 @@ public class StreamThreadTest { public void setUp() { Thread.currentThread().setName(CLIENT_ID + "-StreamThread-" + threadIdx); internalTopologyBuilder.setApplicationId(APPLICATION_ID); - streamsMetadataState = new StreamsMetadataState(internalTopologyBuilder, StreamsMetadataState.UNKNOWN_HOST); + streamsMetadataState = new StreamsMetadataState(new TopologyMetadata(internalTopologyBuilder, config), StreamsMetadataState.UNKNOWN_HOST); } private final String topic1 = "topic1"; @@ -228,7 +227,7 @@ private StreamThread createStreamThread(@SuppressWarnings("SameParameterValue") internalTopologyBuilder.buildTopology(); return StreamThread.create( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), config, clientSupplier, clientSupplier.getAdmin(config.getAdminConfigs(clientId)), @@ -326,23 +325,11 @@ public void shouldChangeStateAtStartClose() throws Exception { } @Test - public void shouldCreateMetricsAtStartupWithBuiltInMetricsVersionLatest() { - shouldCreateMetricsAtStartup(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldCreateMetricsAtStartupWithBuiltInMetricsVersion0100To24() { - shouldCreateMetricsAtStartup(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldCreateMetricsAtStartup(final String builtInMetricsVersion) { - final Properties props = configProps(false); - props.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); - final StreamsConfig config = new StreamsConfig(props); + public void shouldCreateMetricsAtStartup() { final StreamThread thread = createStreamThread(CLIENT_ID, config, false); - final String defaultGroupName = getGroupName(builtInMetricsVersion); + final String defaultGroupName = "stream-thread-metrics"; final Map defaultTags = Collections.singletonMap( - getThreadTagKey(builtInMetricsVersion), + "thread-id", thread.getName() ); final String descriptionIsNotVerified = ""; @@ -404,32 +391,20 @@ private void shouldCreateMetricsAtStartup(final String builtInMetricsVersion) { assertNotNull(metrics.metrics().get(metrics.metricName( "task-closed-total", defaultGroupName, descriptionIsNotVerified, defaultTags))); - if (builtInMetricsVersion.equals(StreamsConfig.METRICS_0100_TO_24)) { - assertNotNull(metrics.metrics().get(metrics.metricName( - "skipped-records-rate", defaultGroupName, descriptionIsNotVerified, defaultTags))); - assertNotNull(metrics.metrics().get(metrics.metricName( - "skipped-records-total", defaultGroupName, descriptionIsNotVerified, defaultTags))); - } else { - assertNull(metrics.metrics().get(metrics.metricName( - "skipped-records-rate", defaultGroupName, descriptionIsNotVerified, defaultTags))); - assertNull(metrics.metrics().get(metrics.metricName( - "skipped-records-total", defaultGroupName, descriptionIsNotVerified, defaultTags))); - } + assertNull(metrics.metrics().get(metrics.metricName( + "skipped-records-rate", defaultGroupName, descriptionIsNotVerified, defaultTags))); + assertNull(metrics.metrics().get(metrics.metricName( + "skipped-records-total", defaultGroupName, descriptionIsNotVerified, defaultTags))); final String taskGroupName = "stream-task-metrics"; final Map taskTags = - mkMap(mkEntry("task-id", "all"), mkEntry(getThreadTagKey(builtInMetricsVersion), thread.getName())); - if (builtInMetricsVersion.equals(StreamsConfig.METRICS_0100_TO_24)) { - assertNotNull(metrics.metrics().get(metrics.metricName( - "commit-rate", taskGroupName, descriptionIsNotVerified, taskTags))); - } else { - assertNull(metrics.metrics().get(metrics.metricName( - "commit-latency-avg", taskGroupName, descriptionIsNotVerified, taskTags))); - assertNull(metrics.metrics().get(metrics.metricName( - "commit-latency-max", taskGroupName, descriptionIsNotVerified, taskTags))); - assertNull(metrics.metrics().get(metrics.metricName( - "commit-rate", taskGroupName, descriptionIsNotVerified, taskTags))); - } + mkMap(mkEntry("task-id", "all"), mkEntry("thread-id", thread.getName())); + assertNull(metrics.metrics().get(metrics.metricName( + "commit-latency-avg", taskGroupName, descriptionIsNotVerified, taskTags))); + assertNull(metrics.metrics().get(metrics.metricName( + "commit-latency-max", taskGroupName, descriptionIsNotVerified, taskTags))); + assertNull(metrics.metrics().get(metrics.metricName( + "commit-rate", taskGroupName, descriptionIsNotVerified, taskTags))); final JmxReporter reporter = new JmxReporter(); final MetricsContext metricsContext = new KafkaMetricsContext("kafka.streams"); @@ -439,29 +414,13 @@ private void shouldCreateMetricsAtStartup(final String builtInMetricsVersion) { assertEquals(CLIENT_ID + "-StreamThread-1", thread.getName()); assertTrue(reporter.containsMbean(String.format("kafka.streams:type=%s,%s=%s", defaultGroupName, - getThreadTagKey(builtInMetricsVersion), + "thread-id", thread.getName()) )); - if (builtInMetricsVersion.equals(StreamsConfig.METRICS_0100_TO_24)) { - assertTrue(reporter.containsMbean(String.format( - "kafka.streams:type=stream-task-metrics,%s=%s,task-id=all", - getThreadTagKey(builtInMetricsVersion), - thread.getName()))); - } else { - assertFalse(reporter.containsMbean(String.format( - "kafka.streams:type=stream-task-metrics,%s=%s,task-id=all", - getThreadTagKey(builtInMetricsVersion), - thread.getName()))); - } - } - - private String getGroupName(final String builtInMetricsVersion) { - return builtInMetricsVersion.equals(StreamsConfig.METRICS_0100_TO_24) ? "stream-metrics" - : "stream-thread-metrics"; - } - - private String getThreadTagKey(final String builtInMetricsVersion) { - return builtInMetricsVersion.equals(StreamsConfig.METRICS_0100_TO_24) ? "client-id" : "thread-id"; + assertFalse(reporter.containsMbean(String.format( + "kafka.streams:type=stream-task-metrics,%s=%s,task-id=all", + "thread-id", + thread.getName()))); } @Test @@ -491,7 +450,7 @@ public void shouldNotCommitBeforeTheCommitInterval() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -531,7 +490,7 @@ public void shouldEnforceRebalanceAfterNextScheduledProbingRebalanceTime() throw mockClientSupplier.setCluster(createCluster()); EasyMock.replay(mockConsumer); final StreamThread thread = StreamThread.create( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), config, mockClientSupplier, mockClientSupplier.getAdmin(config.getAdminConfigs(CLIENT_ID)), @@ -740,7 +699,7 @@ public void shouldNotCauseExceptionIfNothingCommitted() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -810,7 +769,7 @@ int commit(final Collection tasksToCommit) { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -867,7 +826,7 @@ public void shouldRecordCommitLatency() { null, activeTaskCreator, null, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), null, null, null @@ -890,7 +849,7 @@ int commit(final Collection tasksToCommit) { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -1162,6 +1121,8 @@ public void shouldShutdownTaskManagerOnClose() { final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, CLIENT_ID, StreamsConfig.METRICS_LATEST, mockTime); + final TopologyMetadata topologyMetadata = new TopologyMetadata(internalTopologyBuilder, config); + topologyMetadata.buildAndRewriteTopology(); final StreamThread thread = new StreamThread( mockTime, config, @@ -1172,7 +1133,7 @@ public void shouldShutdownTaskManagerOnClose() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + topologyMetadata, CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -1234,7 +1195,7 @@ public void restore(final Map tasks) { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -1278,7 +1239,7 @@ public void shouldShutdownTaskManagerOnCloseWithoutStart() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -1315,7 +1276,7 @@ public void shouldOnlyShutdownOnce() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -1644,7 +1605,7 @@ public void shouldReturnActiveTaskMetadataWhileRunningState() { internalTopologyBuilder.buildTopology(); final StreamThread thread = StreamThread.create( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), config, clientSupplier, clientSupplier.getAdmin(config.getAdminConfigs(CLIENT_ID)), @@ -2134,16 +2095,7 @@ public Set partitions() { } @Test - public void shouldLogAndNotRecordSkippedMetricForDeserializationExceptionWithBuiltInMetricsVersionLatest() { - shouldLogAndRecordSkippedMetricForDeserializationException(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndRecordSkippedMetricForDeserializationExceptionWithBuiltInMetricsVersion0100To24() { - shouldLogAndRecordSkippedMetricForDeserializationException(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndRecordSkippedMetricForDeserializationException(final String builtInMetricsVersion) { + public void shouldLogAndRecordSkippedMetricForDeserializationException() { internalTopologyBuilder.addSource(null, "source1", null, null, null, topic1); final Properties config = configProps(false); @@ -2151,10 +2103,6 @@ private void shouldLogAndRecordSkippedMetricForDeserializationException(final St StreamsConfig.DEFAULT_DESERIALIZATION_EXCEPTION_HANDLER_CLASS_CONFIG, LogAndContinueExceptionHandler.class.getName() ); - config.setProperty( - StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, - builtInMetricsVersion - ); config.setProperty(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.Integer().getClass().getName()); final StreamThread thread = createStreamThread(CLIENT_ID, new StreamsConfig(config), false); @@ -2173,21 +2121,6 @@ private void shouldLogAndRecordSkippedMetricForDeserializationException(final St thread.rebalanceListener().onPartitionsAssigned(assignedPartitions); thread.runOnce(); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - final MetricName skippedTotalMetric = metrics.metricName( - "skipped-records-total", - "stream-metrics", - Collections.singletonMap("client-id", thread.getName()) - ); - final MetricName skippedRateMetric = metrics.metricName( - "skipped-records-rate", - "stream-metrics", - Collections.singletonMap("client-id", thread.getName()) - ); - assertEquals(0.0, metrics.metric(skippedTotalMetric).metricValue()); - assertEquals(0.0, metrics.metric(skippedRateMetric).metricValue()); - } - long offset = -1; mockConsumer.addRecord(new ConsumerRecord<>( t1p1.topic(), @@ -2253,7 +2186,7 @@ public void shouldThrowTaskMigratedExceptionHandlingTaskLost() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -2300,7 +2233,7 @@ public void shouldThrowTaskMigratedExceptionHandlingRevocation() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -2351,6 +2284,8 @@ public void shouldCatchHandleCorruptionOnTaskCorruptedExceptionPath() { final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, CLIENT_ID, StreamsConfig.METRICS_LATEST, mockTime); + final TopologyMetadata topologyMetadata = new TopologyMetadata(internalTopologyBuilder, config); + topologyMetadata.buildAndRewriteTopology(); final StreamThread thread = new StreamThread( mockTime, config, @@ -2361,7 +2296,7 @@ public void shouldCatchHandleCorruptionOnTaskCorruptedExceptionPath() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + topologyMetadata, CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -2414,6 +2349,8 @@ public void shouldCatchTimeoutExceptionFromHandleCorruptionAndInvokeExceptionHan final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, CLIENT_ID, StreamsConfig.METRICS_LATEST, mockTime); + final TopologyMetadata topologyMetadata = new TopologyMetadata(internalTopologyBuilder, config); + topologyMetadata.buildAndRewriteTopology(); final StreamThread thread = new StreamThread( mockTime, config, @@ -2424,7 +2361,7 @@ public void shouldCatchTimeoutExceptionFromHandleCorruptionAndInvokeExceptionHan null, taskManager, streamsMetrics, - internalTopologyBuilder, + topologyMetadata, CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -2485,6 +2422,8 @@ public void shouldCatchTaskMigratedExceptionOnOnTaskCorruptedExceptionPath() { final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, CLIENT_ID, StreamsConfig.METRICS_LATEST, mockTime); + final TopologyMetadata topologyMetadata = new TopologyMetadata(internalTopologyBuilder, config); + topologyMetadata.buildAndRewriteTopology(); final StreamThread thread = new StreamThread( mockTime, config, @@ -2495,7 +2434,7 @@ public void shouldCatchTaskMigratedExceptionOnOnTaskCorruptedExceptionPath() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + topologyMetadata, CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -2561,7 +2500,7 @@ public void shouldNotCommitNonRunningNonRestoringTasks() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -2580,16 +2519,7 @@ public void shouldNotCommitNonRunningNonRestoringTasks() { } @Test - public void shouldLogAndRecordSkippedRecordsForInvalidTimestampsWithBuiltInMetricsVersion0100To24() { - shouldLogAndRecordSkippedRecordsForInvalidTimestamps(StreamsConfig.METRICS_0100_TO_24); - } - - @Test - public void shouldLogAndNotRecordSkippedRecordsForInvalidTimestampsWithBuiltInMetricsVersionLatest() { - shouldLogAndRecordSkippedRecordsForInvalidTimestamps(StreamsConfig.METRICS_LATEST); - } - - private void shouldLogAndRecordSkippedRecordsForInvalidTimestamps(final String builtInMetricsVersion) { + public void shouldLogAndRecordSkippedRecordsForInvalidTimestamps() { internalTopologyBuilder.addSource(null, "source1", null, null, null, topic1); final Properties config = configProps(false); @@ -2597,7 +2527,6 @@ private void shouldLogAndRecordSkippedRecordsForInvalidTimestamps(final String b StreamsConfig.DEFAULT_TIMESTAMP_EXTRACTOR_CLASS_CONFIG, LogAndSkipOnInvalidTimestamp.class.getName() ); - config.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); final StreamThread thread = createStreamThread(CLIENT_ID, new StreamsConfig(config), false); thread.setState(StreamThread.State.STARTING); @@ -2628,33 +2557,18 @@ private void shouldLogAndRecordSkippedRecordsForInvalidTimestamps(final String b Collections.singletonMap("client-id", thread.getName()) ); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals(0.0, metrics.metric(skippedTotalMetric).metricValue()); - assertEquals(0.0, metrics.metric(skippedRateMetric).metricValue()); - } - try (final LogCaptureAppender appender = LogCaptureAppender.createAndRegister(RecordQueue.class)) { long offset = -1; addRecord(mockConsumer, ++offset); addRecord(mockConsumer, ++offset); thread.runOnce(); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals(2.0, metrics.metric(skippedTotalMetric).metricValue()); - assertNotEquals(0.0, metrics.metric(skippedRateMetric).metricValue()); - } - addRecord(mockConsumer, ++offset); addRecord(mockConsumer, ++offset); addRecord(mockConsumer, ++offset); addRecord(mockConsumer, ++offset); thread.runOnce(); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals(6.0, metrics.metric(skippedTotalMetric).metricValue()); - assertNotEquals(0.0, metrics.metric(skippedRateMetric).metricValue()); - } - addRecord(mockConsumer, ++offset, 1L); addRecord(mockConsumer, ++offset, 1L); thread.runOnce(); @@ -2693,11 +2607,6 @@ private void shouldLogAndRecordSkippedRecordsForInvalidTimestamps(final String b "extractor=[org.apache.kafka.streams.processor.LogAndSkipOnInvalidTimestamp]" )); } - - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals(6.0, metrics.metric(skippedTotalMetric).metricValue()); - assertNotEquals(0.0, metrics.metric(skippedRateMetric).metricValue()); - } } @Test @@ -2733,7 +2642,7 @@ public void shouldTransmitTaskManagerMetrics() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -2774,7 +2683,7 @@ public void shouldConstructAdminMetrics() { null, taskManager, streamsMetrics, - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -2817,6 +2726,8 @@ public void runAndVerifyFailedStreamThreadRecording(final boolean shouldFail) { final TaskManager taskManager = EasyMock.createNiceMock(TaskManager.class); final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, CLIENT_ID, StreamsConfig.METRICS_LATEST, mockTime); + final TopologyMetadata topologyMetadata = new TopologyMetadata(internalTopologyBuilder, config); + topologyMetadata.buildAndRewriteTopology(); final StreamThread thread = new StreamThread( mockTime, config, @@ -2827,7 +2738,7 @@ public void runAndVerifyFailedStreamThreadRecording(final boolean shouldFail) { null, taskManager, streamsMetrics, - internalTopologyBuilder, + topologyMetadata, CLIENT_ID, new LogContext(""), new AtomicInteger(), @@ -2885,7 +2796,7 @@ private Collection createStandbyTask() { final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, CLIENT_ID, StreamsConfig.METRICS_LATEST, mockTime); final StandbyTaskCreator standbyTaskCreator = new StandbyTaskCreator( - internalTopologyBuilder, + new TopologyMetadata(internalTopologyBuilder, config), config, streamsMetrics, stateDirectory, diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsAssignmentScaleTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsAssignmentScaleTest.java index a08611fa78b49..2782c1ac9ee8e 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsAssignmentScaleTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsAssignmentScaleTest.java @@ -159,23 +159,22 @@ private void completeLargeAssignment(final int numPartitions, emptySet(), emptySet() ); - + final Map configMap = new HashMap<>(); + configMap.put(StreamsConfig.APPLICATION_ID_CONFIG, APPLICATION_ID); + configMap.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:8080"); final InternalTopologyBuilder builder = new InternalTopologyBuilder(); builder.addSource(null, "source", null, null, null, "topic"); builder.addProcessor("processor", new MockApiProcessorSupplier<>(), "source"); builder.addStateStore(new MockKeyValueStoreBuilder("store", false), "processor"); - builder.setApplicationId(APPLICATION_ID); - builder.buildTopology(); + final TopologyMetadata topologyMetadata = new TopologyMetadata(builder, new StreamsConfig(configMap)); + topologyMetadata.buildAndRewriteTopology(); final Consumer mainConsumer = EasyMock.createNiceMock(Consumer.class); final TaskManager taskManager = EasyMock.createNiceMock(TaskManager.class); - expect(taskManager.builder()).andStubReturn(builder); + expect(taskManager.topologyMetadata()).andStubReturn(topologyMetadata); expect(mainConsumer.committed(new HashSet<>())).andStubReturn(Collections.emptyMap()); final AdminClient adminClient = createMockAdminClientForAssignor(changelogEndOffsets); - final Map configMap = new HashMap<>(); - configMap.put(StreamsConfig.APPLICATION_ID_CONFIG, APPLICATION_ID); - configMap.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:8080"); final ReferenceContainer referenceContainer = new ReferenceContainer(); referenceContainer.mainConsumer = mainConsumer; referenceContainer.adminClient = adminClient; diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsMetadataStateTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsMetadataStateTest.java index 640922c2e70f6..e9492832f53bf 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsMetadataStateTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsMetadataStateTest.java @@ -29,6 +29,7 @@ import org.apache.kafka.streams.kstream.KStream; import org.apache.kafka.streams.kstream.Materialized; import org.apache.kafka.streams.processor.StreamPartitioner; +import org.apache.kafka.streams.processor.internals.testutil.DummyStreamsConfig; import org.apache.kafka.streams.state.HostInfo; import org.apache.kafka.streams.state.StreamsMetadata; import org.junit.Before; @@ -124,7 +125,9 @@ public void before() { new PartitionInfo("topic-four", 0, null, null, null)); cluster = new Cluster(null, Collections.emptyList(), partitionInfos, Collections.emptySet(), Collections.emptySet()); - metadataState = new StreamsMetadataState(TopologyWrapper.getInternalTopologyBuilder(builder.build()), hostOne); + final TopologyMetadata topologyMetadata = new TopologyMetadata(TopologyWrapper.getInternalTopologyBuilder(builder.build()), new DummyStreamsConfig()); + topologyMetadata.buildAndRewriteTopology(); + metadataState = new StreamsMetadataState(topologyMetadata, hostOne); metadataState.onChange(hostToActivePartitions, hostToStandbyPartitions, cluster); partitioner = (topic, key, value, numPartitions) -> 1; storeNames = mkSet("table-one", "table-two", "merged-table", globalTable); @@ -133,7 +136,9 @@ public void before() { @Test public void shouldNotThrowExceptionWhenOnChangeNotCalled() { final Collection metadata = new StreamsMetadataState( - TopologyWrapper.getInternalTopologyBuilder(builder.build()), hostOne).getAllMetadataForStore("store"); + new TopologyMetadata(TopologyWrapper.getInternalTopologyBuilder(builder.build()), new DummyStreamsConfig()), + hostOne + ).getAllMetadataForStore("store"); assertEquals(0, metadata.size()); } @@ -324,7 +329,10 @@ public void shouldGetQueryMetadataForGlobalStoreWithKey() { @Test public void shouldGetAnyHostForGlobalStoreByKeyIfMyHostUnknown() { - final StreamsMetadataState streamsMetadataState = new StreamsMetadataState(TopologyWrapper.getInternalTopologyBuilder(builder.build()), StreamsMetadataState.UNKNOWN_HOST); + final StreamsMetadataState streamsMetadataState = new StreamsMetadataState( + new TopologyMetadata(TopologyWrapper.getInternalTopologyBuilder(builder.build()), new DummyStreamsConfig()), + StreamsMetadataState.UNKNOWN_HOST + ); streamsMetadataState.onChange(hostToActivePartitions, hostToStandbyPartitions, cluster); assertNotNull(streamsMetadataState.getKeyQueryMetadataForKey(globalTable, "key", Serdes.String().serializer())); } @@ -338,7 +346,10 @@ public void shouldGetQueryMetadataForGlobalStoreWithKeyAndPartitioner() { @Test public void shouldGetAnyHostForGlobalStoreByKeyAndPartitionerIfMyHostUnknown() { - final StreamsMetadataState streamsMetadataState = new StreamsMetadataState(TopologyWrapper.getInternalTopologyBuilder(builder.build()), StreamsMetadataState.UNKNOWN_HOST); + final StreamsMetadataState streamsMetadataState = new StreamsMetadataState( + new TopologyMetadata(TopologyWrapper.getInternalTopologyBuilder(builder.build()), new DummyStreamsConfig()), + StreamsMetadataState.UNKNOWN_HOST + ); streamsMetadataState.onChange(hostToActivePartitions, hostToStandbyPartitions, cluster); assertNotNull(streamsMetadataState.getKeyQueryMetadataForKey(globalTable, "key", partitioner)); } diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsPartitionAssignorTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsPartitionAssignorTest.java index 860ed7383b089..532056c6f2b91 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsPartitionAssignorTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamsPartitionAssignorTest.java @@ -190,6 +190,7 @@ public class StreamsPartitionAssignorTest { private TaskManager taskManager; private Admin adminClient; private InternalTopologyBuilder builder = new InternalTopologyBuilder(); + private TopologyMetadata topologyMetadata; private StreamsMetadataState streamsMetadataState = EasyMock.createNiceMock(StreamsMetadataState.class); private final Map subscriptions = new HashMap<>(); private final Class taskAssignor; @@ -240,11 +241,11 @@ private void createDefaultMockTaskManager() { private void createMockTaskManager(final Set activeTasks, final Set standbyTasks) { taskManager = EasyMock.createNiceMock(TaskManager.class); - expect(taskManager.builder()).andStubReturn(builder); + expect(taskManager.topologyMetadata()).andStubReturn(topologyMetadata); expect(taskManager.getTaskOffsetSums()).andStubReturn(getTaskOffsetSums(activeTasks, standbyTasks)); expect(taskManager.processId()).andStubReturn(UUID_1); builder.setApplicationId(APPLICATION_ID); - builder.buildTopology(); + topologyMetadata.buildAndRewriteTopology(); } // If mockCreateInternalTopics is true the internal topic manager will report that it had to create all internal @@ -272,6 +273,7 @@ public static Collection parameters() { public StreamsPartitionAssignorTest(final Class taskAssignor) { this.taskAssignor = taskAssignor; adminClient = createMockAdminClientForAssignor(EMPTY_CHANGELOG_END_OFFSETS); + topologyMetadata = new TopologyMetadata(builder, new StreamsConfig(configProps())); } @Test @@ -822,7 +824,7 @@ public void testAssignWithStates() { private static Set tasksForState(final String storeName, final List tasks, final Map topicGroups) { - final String changelogTopic = ProcessorStateManager.storeChangelogTopic(APPLICATION_ID, storeName); + final String changelogTopic = ProcessorStateManager.storeChangelogTopic(APPLICATION_ID, storeName, null); final Set ids = new HashSet<>(); for (final Map.Entry entry : topicGroups.entrySet()) { @@ -1110,6 +1112,7 @@ public void shouldGenerateTasksForAllCreatedPartitions() { final String client = "client1"; builder = TopologyWrapper.getInternalTopologyBuilder(streamsBuilder.build()); + topologyMetadata = new TopologyMetadata(builder, new StreamsConfig(configProps())); adminClient = createMockAdminClientForAssignor(getTopicPartitionOffsetsMap( asList(APPLICATION_ID + "-topic3-STATE-STORE-0000000002-changelog", @@ -1193,18 +1196,20 @@ public Set makeReady(final Map topics) { @Test public void shouldThrowTimeoutExceptionWhenCreatingChangelogTopicsTimesOut() { + final StreamsConfig config = new StreamsConfig(configProps()); final StreamsBuilder streamsBuilder = new StreamsBuilder(); streamsBuilder.table("topic1", Materialized.as("store")); final String client = "client1"; builder = TopologyWrapper.getInternalTopologyBuilder(streamsBuilder.build()); + topologyMetadata = new TopologyMetadata(builder, config); createDefaultMockTaskManager(); EasyMock.replay(taskManager); partitionAssignor.configure(configProps()); final MockInternalTopicManager mockInternalTopicManager = new MockInternalTopicManager( time, - new StreamsConfig(configProps()), + config, mockClientSupplier.restoreConsumer, false ) { @@ -1436,9 +1441,14 @@ public void shouldTriggerImmediateRebalanceOnTasksRevoked() { @Test public void shouldNotAddStandbyTaskPartitionsToPartitionsForHost() { + final Map props = configProps(); + props.put(StreamsConfig.NUM_STANDBY_REPLICAS_CONFIG, 1); + props.put(StreamsConfig.APPLICATION_SERVER_CONFIG, USER_END_POINT); + final StreamsBuilder streamsBuilder = new StreamsBuilder(); streamsBuilder.stream("topic1").groupByKey().count(); builder = TopologyWrapper.getInternalTopologyBuilder(streamsBuilder.build()); + topologyMetadata = new TopologyMetadata(builder, new StreamsConfig(props)); createDefaultMockTaskManager(); adminClient = createMockAdminClientForAssignor(getTopicPartitionOffsetsMap( @@ -1446,10 +1456,6 @@ public void shouldNotAddStandbyTaskPartitionsToPartitionsForHost() { singletonList(3)) ); - final Map props = new HashMap<>(); - props.put(StreamsConfig.NUM_STANDBY_REPLICAS_CONFIG, 1); - props.put(StreamsConfig.APPLICATION_SERVER_CONFIG, USER_END_POINT); - configurePartitionAssignorWith(props); subscriptions.put("consumer1", @@ -1916,8 +1922,10 @@ public void shouldRequestCommittedOffsetsForPreexistingSourceChangelogs() { streamsBuilder.table("topic1", Materialized.as("store")); final Properties props = new Properties(); + props.putAll(configProps()); props.put(StreamsConfig.TOPOLOGY_OPTIMIZATION_CONFIG, StreamsConfig.OPTIMIZE); builder = TopologyWrapper.getInternalTopologyBuilder(streamsBuilder.build(props)); + topologyMetadata = new TopologyMetadata(builder, new StreamsConfig(props)); subscriptions.put("consumer10", new Subscription( @@ -1992,6 +2000,8 @@ public void testUniqueFieldOverflow() { @Test public void shouldThrowTaskAssignmentExceptionWhenUnableToResolvePartitionCount() { builder = new CorruptedInternalTopologyBuilder(); + topologyMetadata = new TopologyMetadata(builder, new StreamsConfig(configProps())); + final InternalStreamsBuilder streamsBuilder = new InternalStreamsBuilder(builder); final KStream inputTopic = streamsBuilder.stream(singleton("topic1"), new ConsumedInternal<>()); diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/TaskManagerTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/TaskManagerTest.java index 60dd1907fa1bf..16584040f1b9e 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/TaskManagerTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/TaskManagerTest.java @@ -47,6 +47,7 @@ import org.apache.kafka.streams.processor.internals.StreamThread.ProcessingMode; import org.apache.kafka.streams.processor.internals.Task.State; import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl; +import org.apache.kafka.streams.processor.internals.testutil.DummyStreamsConfig; import org.apache.kafka.streams.processor.internals.testutil.LogCaptureAppender; import org.apache.kafka.streams.state.internals.OffsetCheckpoint; @@ -188,12 +189,14 @@ private void setUpTaskManager(final StreamThread.ProcessingMode processingMode) new StreamsMetricsImpl(new Metrics(), "clientId", StreamsConfig.METRICS_LATEST, time), activeTaskCreator, standbyTaskCreator, - topologyBuilder, + new TopologyMetadata(topologyBuilder, new DummyStreamsConfig()), adminClient, stateDirectory, processingMode ); taskManager.setMainConsumer(consumer); + reset(topologyBuilder); + expect(topologyBuilder.hasNamedTopology()).andStubReturn(false); } @Test @@ -202,8 +205,10 @@ public void shouldIdempotentlyUpdateSubscriptionFromActiveAssignment() { final Map> assignment = mkMap(mkEntry(taskId01, mkSet(t1p1, newTopicPartition))); expect(activeTaskCreator.createTasks(anyObject(), eq(assignment))).andStubReturn(emptyList()); + topologyBuilder.addSubscribedTopicsFromAssignment(eq(asList(t1p1, newTopicPartition)), anyString()); expectLastCall(); + replay(activeTaskCreator, topologyBuilder); taskManager.handleAssignment(assignment, emptyMap()); diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/ProcessorNodeMetricsTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/ProcessorNodeMetricsTest.java index 08131e6309ca6..8cc2dc3238936 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/ProcessorNodeMetricsTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/ProcessorNodeMetricsTest.java @@ -22,15 +22,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.modules.junit4.PowerMockRunnerDelegate; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.function.Supplier; @@ -46,7 +40,6 @@ import static org.powermock.api.easymock.PowerMock.verify; @RunWith(PowerMockRunner.class) -@PowerMockRunnerDelegate(Parameterized.class) @PrepareForTest({StreamsMetricsImpl.class, Sensor.class}) public class ProcessorNodeMetricsTest { @@ -60,20 +53,9 @@ public class ProcessorNodeMetricsTest { private final Map tagMap = Collections.singletonMap("hello", "world"); private final Map parentTagMap = Collections.singletonMap("hi", "universe"); - @Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][]{ - {Version.LATEST}, - {Version.FROM_0100_TO_24} - }); - } - - @Parameter - public Version builtInMetricsVersion; - @Before public void setUp() { - expect(streamsMetrics.version()).andReturn(builtInMetricsVersion).anyTimes(); + expect(streamsMetrics.version()).andStubReturn(Version.LATEST); mockStatic(StreamsMetricsImpl.class); } @@ -120,23 +102,6 @@ public void shouldGetIdempotentUpdateSkipSensor() { ); } - @Test - public void shouldGetProcessSensor() { - final String metricNamePrefix = "process"; - final String descriptionOfCount = "The total number of calls to process"; - final String descriptionOfRate = "The average number of calls to process per second"; - final String descriptionOfAvgLatency = "The average latency of calls to process"; - final String descriptionOfMaxLatency = "The maximum latency of calls to process"; - shouldGetThroughputAndLatencySensorWithParentOrEmptySensor( - metricNamePrefix, - descriptionOfRate, - descriptionOfCount, - descriptionOfAvgLatency, - descriptionOfMaxLatency, - () -> ProcessorNodeMetrics.processSensor(THREAD_ID, TASK_ID, PROCESSOR_NODE_ID, streamsMetrics) - ); - } - @Test public void shouldGetProcessAtSourceSensor() { final String metricNamePrefix = "process"; @@ -165,57 +130,6 @@ public void shouldGetProcessAtSourceSensor() { verifySensor(() -> ProcessorNodeMetrics.processAtSourceSensor(THREAD_ID, TASK_ID, PROCESSOR_NODE_ID, streamsMetrics)); } - @Test - public void shouldGetPunctuateSensor() { - final String metricNamePrefix = "punctuate"; - final String descriptionOfCount = "The total number of calls to punctuate"; - final String descriptionOfRate = "The average number of calls to punctuate per second"; - final String descriptionOfAvgLatency = "The average latency of calls to punctuate"; - final String descriptionOfMaxLatency = "The maximum latency of calls to punctuate"; - shouldGetThroughputAndLatencySensorWithParentOrEmptySensor( - metricNamePrefix, - descriptionOfRate, - descriptionOfCount, - descriptionOfAvgLatency, - descriptionOfMaxLatency, - () -> ProcessorNodeMetrics.punctuateSensor(THREAD_ID, TASK_ID, PROCESSOR_NODE_ID, streamsMetrics) - ); - } - - @Test - public void shouldGetCreateSensor() { - final String metricNamePrefix = "create"; - final String descriptionOfCount = "The total number of processor nodes created"; - final String descriptionOfRate = "The average number of processor nodes created per second"; - final String descriptionOfAvgLatency = "The average latency of creations of processor nodes"; - final String descriptionOfMaxLatency = "The maximum latency of creations of processor nodes"; - shouldGetThroughputAndLatencySensorWithParentOrEmptySensor( - metricNamePrefix, - descriptionOfRate, - descriptionOfCount, - descriptionOfAvgLatency, - descriptionOfMaxLatency, - () -> ProcessorNodeMetrics.createSensor(THREAD_ID, TASK_ID, PROCESSOR_NODE_ID, streamsMetrics) - ); - } - - @Test - public void shouldGetDestroySensor() { - final String metricNamePrefix = "destroy"; - final String descriptionOfCount = "The total number of destructions of processor nodes"; - final String descriptionOfRate = "The average number of destructions of processor nodes per second"; - final String descriptionOfAvgLatency = "The average latency of destructions of processor nodes"; - final String descriptionOfMaxLatency = "The maximum latency of destructions of processor nodes"; - shouldGetThroughputAndLatencySensorWithParentOrEmptySensor( - metricNamePrefix, - descriptionOfRate, - descriptionOfCount, - descriptionOfAvgLatency, - descriptionOfMaxLatency, - () -> ProcessorNodeMetrics.destroySensor(THREAD_ID, TASK_ID, PROCESSOR_NODE_ID, streamsMetrics) - ); - } - @Test public void shouldGetForwardSensor() { final String metricNamePrefix = "forward"; @@ -252,91 +166,6 @@ public void shouldGetLateRecordDropSensor() { verifySensor(() -> ProcessorNodeMetrics.lateRecordDropSensor(THREAD_ID, TASK_ID, PROCESSOR_NODE_ID, streamsMetrics)); } - @Test - public void shouldGetProcessAtSourceSensorOrForwardSensor() { - if (builtInMetricsVersion == Version.FROM_0100_TO_24) { - shouldGetForwardSensor(); - } else { - shouldGetProcessAtSourceSensor(); - } - } - - private void shouldGetThroughputAndLatencySensorWithParentOrEmptySensor(final String metricNamePrefix, - final String descriptionOfRate, - final String descriptionOfCount, - final String descriptionOfAvgLatency, - final String descriptionOfMaxLatency, - final Supplier sensorSupplier) { - if (builtInMetricsVersion == Version.FROM_0100_TO_24) { - setUpThroughputAndLatencySensorWithParent( - metricNamePrefix, - descriptionOfRate, - descriptionOfCount, - descriptionOfAvgLatency, - descriptionOfMaxLatency - ); - } else { - expect(streamsMetrics.nodeLevelSensor( - THREAD_ID, - TASK_ID, - PROCESSOR_NODE_ID, - metricNamePrefix, - RecordingLevel.DEBUG - )).andReturn(expectedSensor); - } - - verifySensor(sensorSupplier); - } - - private void setUpThroughputAndLatencySensorWithParent(final String metricNamePrefix, - final String descriptionOfRate, - final String descriptionOfCount, - final String descriptionOfAvgLatency, - final String descriptionOfMaxLatency) { - setUpThroughputAndLatencyParentSensor( - metricNamePrefix, - descriptionOfRate, - descriptionOfCount, - descriptionOfAvgLatency, - descriptionOfMaxLatency - ); - setUpThroughputAndLatencySensor( - metricNamePrefix, - descriptionOfRate, - descriptionOfCount, - descriptionOfAvgLatency, - descriptionOfMaxLatency, - expectedParentSensor - ); - } - - private void setUpThroughputAndLatencyParentSensor(final String metricNamePrefix, - final String descriptionOfRate, - final String descriptionOfCount, - final String descriptionOfAvg, - final String descriptionOfMax) { - expect(streamsMetrics.taskLevelSensor(THREAD_ID, TASK_ID, metricNamePrefix, RecordingLevel.DEBUG)) - .andReturn(expectedParentSensor); - expect(streamsMetrics.nodeLevelTagMap(THREAD_ID, TASK_ID, StreamsMetricsImpl.ROLLUP_VALUE)) - .andReturn(parentTagMap); - StreamsMetricsImpl.addInvocationRateAndCountToSensor( - expectedParentSensor, - PROCESSOR_NODE_LEVEL_GROUP, - parentTagMap, - metricNamePrefix, - descriptionOfRate, - descriptionOfCount - ); - StreamsMetricsImpl.addAvgAndMaxToSensor( - expectedParentSensor, - PROCESSOR_NODE_LEVEL_GROUP, - parentTagMap, - metricNamePrefix + StreamsMetricsImpl.LATENCY_SUFFIX, - descriptionOfAvg, - descriptionOfMax - ); - } - private void setUpThroughputParentSensor(final String metricNamePrefix, final String descriptionOfRate, final String descriptionOfCount) { @@ -354,23 +183,6 @@ private void setUpThroughputParentSensor(final String metricNamePrefix, ); } - private void setUpThroughputAndLatencySensor(final String metricNamePrefix, - final String descriptionOfRate, - final String descriptionOfCount, - final String descriptionOfAvgLatency, - final String descriptionOfMaxLatency, - final Sensor... parentSensors) { - setUpThroughputSensor(metricNamePrefix, descriptionOfRate, descriptionOfCount, RecordingLevel.DEBUG, parentSensors); - StreamsMetricsImpl.addAvgAndMaxToSensor( - expectedSensor, - PROCESSOR_NODE_LEVEL_GROUP, - tagMap, - metricNamePrefix + StreamsMetricsImpl.LATENCY_SUFFIX, - descriptionOfAvgLatency, - descriptionOfMaxLatency - ); - } - private void setUpThroughputSensor(final String metricNamePrefix, final String descriptionOfRate, final String descriptionOfCount, diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/StreamsMetricsImplTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/StreamsMetricsImplTest.java index fdb96db6868e7..bfe05a6fc48ce 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/StreamsMetricsImplTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/StreamsMetricsImplTest.java @@ -100,7 +100,6 @@ public class StreamsMetricsImplTest { private final static String METRIC_NAME1 = "test-metric1"; private final static String METRIC_NAME2 = "test-metric2"; private final static String THREAD_ID_TAG = "thread-id"; - private final static String THREAD_ID_TAG_0100_TO_24 = "client-id"; private final static String TASK_ID_TAG = "task-id"; private final static String SCOPE_NAME = "test-scope"; private final static String STORE_ID_TAG = "-state-id"; @@ -843,17 +842,8 @@ public void testTotalMetricDoesntDecrease() { } @Test - public void shouldAddLatencyRateTotalSensorWithBuiltInMetricsVersionLatest() { - shouldAddLatencyRateTotalSensor(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldAddLatencyRateTotalSensorWithBuiltInMetricsVersion0100To24() { - shouldAddLatencyRateTotalSensor(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldAddLatencyRateTotalSensor(final String builtInMetricsVersion) { - final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, CLIENT_ID, builtInMetricsVersion, time); + public void shouldAddLatencyRateTotalSensor() { + final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, CLIENT_ID, VERSION, time); shouldAddCustomSensor( streamsMetrics.addLatencyRateTotalSensor(SCOPE_NAME, ENTITY_NAME, OPERATION_NAME, RecordingLevel.DEBUG), streamsMetrics, @@ -867,17 +857,8 @@ private void shouldAddLatencyRateTotalSensor(final String builtInMetricsVersion) } @Test - public void shouldAddRateTotalSensorWithBuiltInMetricsVersionLatest() { - shouldAddRateTotalSensor(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldAddRateTotalSensorWithBuiltInMetricsVersion0100To24() { - shouldAddRateTotalSensor(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldAddRateTotalSensor(final String builtInMetricsVersion) { - final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, CLIENT_ID, builtInMetricsVersion, time); + public void shouldAddRateTotalSensor() { + final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, CLIENT_ID, VERSION, time); shouldAddCustomSensor( streamsMetrics.addRateTotalSensor(SCOPE_NAME, ENTITY_NAME, OPERATION_NAME, RecordingLevel.DEBUG), streamsMetrics, @@ -1008,54 +989,34 @@ public void shouldGetClientLevelTagMap() { } @Test - public void shouldGetStoreLevelTagMapForBuiltInMetricsLatestVersion() { - shouldGetStoreLevelTagMap(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldGetStoreLevelTagMapForBuiltInMetricsVersion0100To24() { - shouldGetStoreLevelTagMap(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldGetStoreLevelTagMap(final String builtInMetricsVersion) { + public void shouldGetStoreLevelTagMap() { final String taskName = "test-task"; final String storeType = "remote-window"; final String storeName = "window-keeper"; - final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, THREAD_ID1, builtInMetricsVersion, time); + final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, THREAD_ID1, VERSION, time); final Map tagMap = streamsMetrics.storeLevelTagMap(taskName, storeType, storeName); assertThat(tagMap.size(), equalTo(3)); - final boolean isMetricsLatest = builtInMetricsVersion.equals(StreamsConfig.METRICS_LATEST); assertThat( - tagMap.get(isMetricsLatest ? StreamsMetricsImpl.THREAD_ID_TAG : StreamsMetricsImpl.THREAD_ID_TAG_0100_TO_24), + tagMap.get(StreamsMetricsImpl.THREAD_ID_TAG), equalTo(Thread.currentThread().getName())); assertThat(tagMap.get(StreamsMetricsImpl.TASK_ID_TAG), equalTo(taskName)); assertThat(tagMap.get(storeType + "-" + StreamsMetricsImpl.STORE_ID_TAG), equalTo(storeName)); } @Test - public void shouldGetCacheLevelTagMapForBuiltInMetricsLatestVersion() { - shouldGetCacheLevelTagMap(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldGetCacheLevelTagMapForBuiltInMetricsVersion0100To24() { - shouldGetCacheLevelTagMap(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldGetCacheLevelTagMap(final String builtInMetricsVersion) { + public void shouldGetCacheLevelTagMap() { final StreamsMetricsImpl streamsMetrics = - new StreamsMetricsImpl(metrics, THREAD_ID1, builtInMetricsVersion, time); + new StreamsMetricsImpl(metrics, THREAD_ID1, VERSION, time); final String taskName = "taskName"; final String storeName = "storeName"; final Map tagMap = streamsMetrics.cacheLevelTagMap(THREAD_ID1, taskName, storeName); assertThat(tagMap.size(), equalTo(3)); - final boolean isMetricsLatest = builtInMetricsVersion.equals(StreamsConfig.METRICS_LATEST); assertThat( - tagMap.get(isMetricsLatest ? StreamsMetricsImpl.THREAD_ID_TAG : StreamsMetricsImpl.THREAD_ID_TAG_0100_TO_24), + tagMap.get(StreamsMetricsImpl.THREAD_ID_TAG), equalTo(THREAD_ID1) ); assertThat(tagMap.get(TASK_ID_TAG), equalTo(taskName)); @@ -1063,24 +1024,14 @@ private void shouldGetCacheLevelTagMap(final String builtInMetricsVersion) { } @Test - public void shouldGetThreadLevelTagMapForBuiltInMetricsLatestVersion() { - shouldGetThreadLevelTagMap(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldGetThreadLevelTagMapForBuiltInMetricsVersion0100To24() { - shouldGetThreadLevelTagMap(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldGetThreadLevelTagMap(final String builtInMetricsVersion) { - final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, THREAD_ID1, builtInMetricsVersion, time); + public void shouldGetThreadLevelTagMap() { + final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, THREAD_ID1, VERSION, time); final Map tagMap = streamsMetrics.threadLevelTagMap(THREAD_ID1); assertThat(tagMap.size(), equalTo(1)); assertThat( - tagMap.get(builtInMetricsVersion.equals(StreamsConfig.METRICS_LATEST) ? THREAD_ID_TAG - : THREAD_ID_TAG_0100_TO_24), + tagMap.get(THREAD_ID_TAG), equalTo(THREAD_ID1) ); } @@ -1199,14 +1150,6 @@ public void shouldReturnMetricsVersionCurrent() { ); } - @Test - public void shouldReturnMetricsVersionFrom100To23() { - assertThat( - new StreamsMetricsImpl(metrics, THREAD_ID1, StreamsConfig.METRICS_0100_TO_24, time).version(), - equalTo(Version.FROM_0100_TO_24) - ); - } - private void verifyMetric(final String name, final String description, final double valueToRecord1, diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/TaskMetricsTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/TaskMetricsTest.java index 01f67d03f4beb..1c31390c89bb5 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/TaskMetricsTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/TaskMetricsTest.java @@ -23,15 +23,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.modules.junit4.PowerMockRunnerDelegate; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Map; @@ -45,7 +39,6 @@ import static org.powermock.api.easymock.PowerMock.verify; @RunWith(PowerMockRunner.class) -@PowerMockRunnerDelegate(Parameterized.class) @PrepareForTest({StreamsMetricsImpl.class, Sensor.class, ThreadMetrics.class, StateStoreMetrics.class, ProcessorNodeMetrics.class}) public class TaskMetricsTest { @@ -56,20 +49,9 @@ public class TaskMetricsTest { private final Sensor expectedSensor = createMock(Sensor.class); private final Map tagMap = Collections.singletonMap("hello", "world"); - @Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][] { - {Version.LATEST}, - {Version.FROM_0100_TO_24} - }); - } - - @Parameter - public Version builtInMetricsVersion; - @Before public void setUp() { - expect(streamsMetrics.version()).andReturn(builtInMetricsVersion).anyTimes(); + expect(streamsMetrics.version()).andStubReturn(Version.LATEST); mockStatic(StreamsMetricsImpl.class); } @@ -126,19 +108,17 @@ public void shouldGetProcessLatencySensor() { final String operation = "process-latency"; expect(streamsMetrics.taskLevelSensor(THREAD_ID, TASK_ID, operation, RecordingLevel.DEBUG)) .andReturn(expectedSensor); - if (builtInMetricsVersion == Version.LATEST) { - final String avgLatencyDescription = "The average latency of calls to process"; - final String maxLatencyDescription = "The maximum latency of calls to process"; - expect(streamsMetrics.taskLevelTagMap(THREAD_ID, TASK_ID)).andReturn(tagMap); - StreamsMetricsImpl.addAvgAndMaxToSensor( - expectedSensor, - TASK_LEVEL_GROUP, - tagMap, - operation, - avgLatencyDescription, - maxLatencyDescription - ); - } + final String avgLatencyDescription = "The average latency of calls to process"; + final String maxLatencyDescription = "The maximum latency of calls to process"; + expect(streamsMetrics.taskLevelTagMap(THREAD_ID, TASK_ID)).andReturn(tagMap); + StreamsMetricsImpl.addAvgAndMaxToSensor( + expectedSensor, + TASK_LEVEL_GROUP, + tagMap, + operation, + avgLatencyDescription, + maxLatencyDescription + ); replay(StreamsMetricsImpl.class, streamsMetrics); final Sensor sensor = TaskMetrics.processLatencySensor(THREAD_ID, TASK_ID, streamsMetrics); @@ -152,30 +132,28 @@ public void shouldGetPunctuateSensor() { final String operation = "punctuate"; expect(streamsMetrics.taskLevelSensor(THREAD_ID, TASK_ID, operation, RecordingLevel.DEBUG)) .andReturn(expectedSensor); - if (builtInMetricsVersion == Version.LATEST) { - final String operationLatency = operation + StreamsMetricsImpl.LATENCY_SUFFIX; - final String totalDescription = "The total number of calls to punctuate"; - final String rateDescription = "The average number of calls to punctuate per second"; - final String avgLatencyDescription = "The average latency of calls to punctuate"; - final String maxLatencyDescription = "The maximum latency of calls to punctuate"; - expect(streamsMetrics.taskLevelTagMap(THREAD_ID, TASK_ID)).andReturn(tagMap); - StreamsMetricsImpl.addInvocationRateAndCountToSensor( - expectedSensor, - TASK_LEVEL_GROUP, - tagMap, - operation, - rateDescription, - totalDescription - ); - StreamsMetricsImpl.addAvgAndMaxToSensor( - expectedSensor, - TASK_LEVEL_GROUP, - tagMap, - operationLatency, - avgLatencyDescription, - maxLatencyDescription - ); - } + final String operationLatency = operation + StreamsMetricsImpl.LATENCY_SUFFIX; + final String totalDescription = "The total number of calls to punctuate"; + final String rateDescription = "The average number of calls to punctuate per second"; + final String avgLatencyDescription = "The average latency of calls to punctuate"; + final String maxLatencyDescription = "The maximum latency of calls to punctuate"; + expect(streamsMetrics.taskLevelTagMap(THREAD_ID, TASK_ID)).andReturn(tagMap); + StreamsMetricsImpl.addInvocationRateAndCountToSensor( + expectedSensor, + TASK_LEVEL_GROUP, + tagMap, + operation, + rateDescription, + totalDescription + ); + StreamsMetricsImpl.addAvgAndMaxToSensor( + expectedSensor, + TASK_LEVEL_GROUP, + tagMap, + operationLatency, + avgLatencyDescription, + maxLatencyDescription + ); replay(StreamsMetricsImpl.class, streamsMetrics); final Sensor sensor = TaskMetrics.punctuateSensor(THREAD_ID, TASK_ID, streamsMetrics); @@ -279,76 +257,4 @@ public void shouldGetDroppedRecordsSensor() { verify(StreamsMetricsImpl.class, streamsMetrics); assertThat(sensor, is(expectedSensor)); } - - @Test - public void shouldGetDroppedRecordsSensorOrSkippedRecordsSensor() { - mockStatic(ThreadMetrics.class); - if (builtInMetricsVersion == Version.FROM_0100_TO_24) { - expect(ThreadMetrics.skipRecordSensor(THREAD_ID, streamsMetrics)).andReturn(expectedSensor); - replay(ThreadMetrics.class, StreamsMetricsImpl.class, streamsMetrics); - - final Sensor sensor = TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor(THREAD_ID, TASK_ID, streamsMetrics); - - verify(ThreadMetrics.class); - assertThat(sensor, is(expectedSensor)); - } else { - shouldGetDroppedRecordsSensor(); - } - } - - @Test - public void shouldGetDroppedRecordsSensorOrExpiredWindowRecordDropSensor() { - final String storeType = "test-store-type"; - final String storeName = "test-store-name"; - mockStatic(StateStoreMetrics.class); - if (builtInMetricsVersion == Version.FROM_0100_TO_24) { - expect(StateStoreMetrics.expiredWindowRecordDropSensor( - TASK_ID, - storeType, - storeName, - streamsMetrics - )).andReturn(expectedSensor); - replay(StateStoreMetrics.class, StreamsMetricsImpl.class, streamsMetrics); - - final Sensor sensor = TaskMetrics.droppedRecordsSensorOrExpiredWindowRecordDropSensor( - THREAD_ID, - TASK_ID, - storeType, - storeName, - streamsMetrics - ); - - verify(StateStoreMetrics.class); - assertThat(sensor, is(expectedSensor)); - } else { - shouldGetDroppedRecordsSensor(); - } - } - - @Test - public void shouldGetDroppedRecordsSensorOrLateRecordDropSensor() { - final String processorNodeId = "test-processor-node"; - mockStatic(ProcessorNodeMetrics.class); - if (builtInMetricsVersion == Version.FROM_0100_TO_24) { - expect(ProcessorNodeMetrics.lateRecordDropSensor( - THREAD_ID, - TASK_ID, - processorNodeId, - streamsMetrics - )).andReturn(expectedSensor); - replay(ProcessorNodeMetrics.class, StreamsMetricsImpl.class, streamsMetrics); - - final Sensor sensor = TaskMetrics.droppedRecordsSensorOrLateRecordDropSensor( - THREAD_ID, - TASK_ID, - processorNodeId, - streamsMetrics - ); - - verify(ProcessorNodeMetrics.class); - assertThat(sensor, is(expectedSensor)); - } else { - shouldGetDroppedRecordsSensor(); - } - } } \ No newline at end of file diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/ThreadMetricsTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/ThreadMetricsTest.java index 1a545c0a0d4a8..7a9a0945ca9a7 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/ThreadMetricsTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/metrics/ThreadMetricsTest.java @@ -22,15 +22,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.modules.junit4.PowerMockRunnerDelegate; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Map; @@ -47,12 +41,10 @@ import static org.powermock.api.easymock.PowerMock.verify; @RunWith(PowerMockRunner.class) -@PowerMockRunnerDelegate(Parameterized.class) @PrepareForTest({StreamsMetricsImpl.class, Sensor.class}) public class ThreadMetricsTest { private static final String THREAD_ID = "thread-id"; - private static final String THREAD_LEVEL_GROUP_0100_TO_24 = "stream-metrics"; private static final String THREAD_LEVEL_GROUP = "stream-thread-metrics"; private static final String TASK_LEVEL_GROUP = "stream-task-metrics"; @@ -60,23 +52,9 @@ public class ThreadMetricsTest { private final StreamsMetricsImpl streamsMetrics = createMock(StreamsMetricsImpl.class); private final Map tagMap = Collections.singletonMap("hello", "world"); - @Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][] { - {Version.LATEST, THREAD_LEVEL_GROUP}, - {Version.FROM_0100_TO_24, THREAD_LEVEL_GROUP_0100_TO_24} - }); - } - - @Parameter - public Version builtInMetricsVersion; - - @Parameter(1) - public String threadLevelGroup; - @Before public void setUp() { - expect(streamsMetrics.version()).andReturn(builtInMetricsVersion).anyTimes(); + expect(streamsMetrics.version()).andStubReturn(Version.LATEST); mockStatic(StreamsMetricsImpl.class); } @@ -88,7 +66,7 @@ public void shouldGetProcessRatioSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addValueMetricToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, ratioDescription @@ -110,7 +88,7 @@ public void shouldGetProcessRecordsSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addAvgAndMaxToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, avgDescription, @@ -133,7 +111,7 @@ public void shouldGetProcessLatencySensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addAvgAndMaxToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operationLatency, avgLatencyDescription, @@ -157,7 +135,7 @@ public void shouldGetProcessRateSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addRateOfSumAndSumMetricsToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, rateDescription, @@ -179,7 +157,7 @@ public void shouldGetPollRatioSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addValueMetricToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, ratioDescription @@ -201,7 +179,7 @@ public void shouldGetPollRecordsSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addAvgAndMaxToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, avgDescription, @@ -227,7 +205,7 @@ public void shouldGetPollSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addInvocationRateAndCountToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, rateDescription, @@ -235,7 +213,7 @@ public void shouldGetPollSensor() { ); StreamsMetricsImpl.addAvgAndMaxToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operationLatency, avgLatencyDescription, @@ -261,7 +239,7 @@ public void shouldGetCommitSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addInvocationRateAndCountToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, rateDescription, @@ -269,7 +247,7 @@ public void shouldGetCommitSensor() { ); StreamsMetricsImpl.addAvgAndMaxToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operationLatency, avgLatencyDescription, @@ -290,7 +268,7 @@ public void shouldGetCommitRatioSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addValueMetricToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, ratioDescription @@ -306,15 +284,10 @@ public void shouldGetCommitRatioSensor() { @Test public void shouldGetCommitOverTasksSensor() { final String operation = "commit"; - final String operationLatency = operation + StreamsMetricsImpl.LATENCY_SUFFIX; final String totalDescription = "The total number of calls to commit over all tasks assigned to one stream thread"; final String rateDescription = "The average per-second number of calls to commit over all tasks assigned to one stream thread"; - final String avgLatencyDescription = - "The average commit latency over all tasks assigned to one stream thread"; - final String maxLatencyDescription = - "The maximum commit latency over all tasks assigned to one stream thread"; expect(streamsMetrics.threadLevelSensor(THREAD_ID, operation, RecordingLevel.DEBUG)).andReturn(expectedSensor); expect(streamsMetrics.taskLevelTagMap(THREAD_ID, ROLLUP_VALUE)).andReturn(tagMap); StreamsMetricsImpl.addInvocationRateAndCountToSensor( @@ -345,7 +318,7 @@ public void shouldGetPunctuateSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addInvocationRateAndCountToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, rateDescription, @@ -353,7 +326,7 @@ public void shouldGetPunctuateSensor() { ); StreamsMetricsImpl.addAvgAndMaxToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operationLatency, avgLatencyDescription, @@ -375,7 +348,7 @@ public void shouldGetPunctuateRatioSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addValueMetricToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, ratioDescription @@ -398,7 +371,7 @@ public void shouldGetSkipRecordSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addInvocationRateAndCountToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, rateDescription, @@ -422,7 +395,7 @@ public void shouldGetCreateTaskSensor() { StreamsMetricsImpl.addInvocationRateAndCountToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, rateDescription, @@ -446,7 +419,7 @@ public void shouldGetCloseTaskSensor() { expect(streamsMetrics.threadLevelTagMap(THREAD_ID)).andReturn(tagMap); StreamsMetricsImpl.addInvocationRateAndCountToSensor( expectedSensor, - threadLevelGroup, + THREAD_LEVEL_GROUP, tagMap, operation, rateDescription, diff --git a/clients/src/main/java/org/apache/kafka/common/metrics/stats/Count.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/testutil/DummyStreamsConfig.java similarity index 57% rename from clients/src/main/java/org/apache/kafka/common/metrics/stats/Count.java rename to streams/src/test/java/org/apache/kafka/streams/processor/internals/testutil/DummyStreamsConfig.java index 2bef7cf93d8bb..017461941e615 100644 --- a/clients/src/main/java/org/apache/kafka/common/metrics/stats/Count.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/testutil/DummyStreamsConfig.java @@ -14,16 +14,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.kafka.common.metrics.stats; +package org.apache.kafka.streams.processor.internals.testutil; -/** - * A {@link SampledStat} that maintains a simple count of what it has seen. - * This is a special kind of {@link WindowedSum} that always records a value of {@code 1} instead of the provided value. - * - * See also {@link CumulativeCount} for a non-sampled version of this metric. - * - * @deprecated since 2.4 . Use {@link WindowedCount} instead - */ -@Deprecated -public class Count extends WindowedCount { +import org.apache.kafka.streams.StreamsConfig; + +import java.util.Properties; + +public class DummyStreamsConfig extends StreamsConfig { + + private final static Properties PROPS = dummyProps(); + + public DummyStreamsConfig() { + super(PROPS); + } + + private static Properties dummyProps() { + final Properties props = new Properties(); + props.put(StreamsConfig.APPLICATION_ID_CONFIG, "dummy-application"); + props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:2171"); + return props; + } } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/KeyValueStoreTestDriver.java b/streams/src/test/java/org/apache/kafka/streams/state/KeyValueStoreTestDriver.java index 38e98603c7be0..3f438f939b821 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/KeyValueStoreTestDriver.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/KeyValueStoreTestDriver.java @@ -187,6 +187,7 @@ public static KeyValueStoreTestDriver create(final Serializer ke private final InternalMockProcessorContext context; private final StateSerdes stateSerdes; + @SuppressWarnings("unchecked") private KeyValueStoreTestDriver(final StateSerdes serdes) { props = new Properties(); props.put(StreamsConfig.APPLICATION_ID_CONFIG, "application-id"); diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractKeyValueStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractKeyValueStoreTest.java index 5959a8079106d..e050a21b2feb6 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractKeyValueStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractKeyValueStoreTest.java @@ -49,6 +49,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +@SuppressWarnings("unchecked") public abstract class AbstractKeyValueStoreTest { protected abstract KeyValueStore createKeyValueStore(final StateStoreContext context); @@ -80,6 +81,7 @@ private static Map getContents(final KeyValueIterator( stateDir, Serdes.String(), Serdes.Long(), @@ -397,18 +397,8 @@ private void shouldRestoreToByteStore(final TaskType taskType) { } @Test - public void shouldLogAndMeasureExpiredRecordsWithBuiltInMetricsVersionLatest() { - shouldLogAndMeasureExpiredRecords(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeasureExpiredRecordsWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeasureExpiredRecords(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeasureExpiredRecords(final String builtInMetricsVersion) { + public void shouldLogAndMeasureExpiredRecords() { final Properties streamsConfig = StreamsTestUtils.getStreamsConfig(); - streamsConfig.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); final AbstractRocksDBSegmentedBytesStore bytesStore = getBytesStore(); final InternalMockProcessorContext context = new InternalMockProcessorContext( TestUtils.tempDirectory(), @@ -435,49 +425,25 @@ private void shouldLogAndMeasureExpiredRecords(final String builtInMetricsVersio final String threadId = Thread.currentThread().getName(); final Metric dropTotal; final Metric dropRate; - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - dropTotal = metrics.get(new MetricName( - "expired-window-record-drop-total", - "stream-metrics-scope-metrics", - "The total number of dropped records due to an expired window", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry("metrics-scope-state-id", "bytes-store") - ) - )); - - dropRate = metrics.get(new MetricName( - "expired-window-record-drop-rate", - "stream-metrics-scope-metrics", - "The average number of dropped records due to an expired window per second.", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry("metrics-scope-state-id", "bytes-store") - ) - )); - } else { - dropTotal = metrics.get(new MetricName( - "dropped-records-total", - "stream-task-metrics", - "", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - )); - - dropRate = metrics.get(new MetricName( - "dropped-records-rate", - "stream-task-metrics", - "", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - )); - } + dropTotal = metrics.get(new MetricName( + "dropped-records-total", + "stream-task-metrics", + "", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + )); + + dropRate = metrics.get(new MetricName( + "dropped-records-rate", + "stream-task-metrics", + "", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + )); assertEquals(1.0, dropTotal.metricValue()); assertNotEquals(0.0, dropRate.metricValue()); diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractSessionBytesStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractSessionBytesStoreTest.java index ed60837400d2f..6a4fb7113f017 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractSessionBytesStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractSessionBytesStoreTest.java @@ -83,13 +83,11 @@ abstract SessionStore buildSessionStore(final long retentionPeriod, final Serde keySerde, final Serde valueSerde); - abstract String getMetricsScope(); - @Before public void setUp() { sessionStore = buildSessionStore(RETENTION_PERIOD, Serdes.String(), Serdes.Long()); recordCollector = new MockRecordCollector(); - context = new InternalMockProcessorContext( + context = new InternalMockProcessorContext<>( TestUtils.tempDirectory(), Serdes.String(), Serdes.Long(), @@ -536,6 +534,7 @@ public void testIteratorPeekBackward() { assertFalse(iterator.hasNext()); } + @SuppressWarnings("unchecked") @Test public void shouldRestore() { final List, Long>> expected = Arrays.asList( @@ -601,18 +600,8 @@ public void shouldReturnSameResultsForSingleKeyFindSessionsAndEqualKeyRangeFindS } @Test - public void shouldLogAndMeasureExpiredRecordsWithBuiltInMetricsVersionLatest() { - shouldLogAndMeasureExpiredRecords(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeasureExpiredRecordsWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeasureExpiredRecords(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeasureExpiredRecords(final String builtInMetricsVersion) { + public void shouldLogAndMeasureExpiredRecords() { final Properties streamsConfig = StreamsTestUtils.getStreamsConfig(); - streamsConfig.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); final SessionStore sessionStore = buildSessionStore(RETENTION_PERIOD, Serdes.String(), Serdes.Long()); final InternalMockProcessorContext context = new InternalMockProcessorContext( TestUtils.tempDirectory(), @@ -638,53 +627,28 @@ private void shouldLogAndMeasureExpiredRecords(final String builtInMetricsVersio } final Map metrics = context.metrics().metrics(); - final String metricScope = getMetricsScope(); final String threadId = Thread.currentThread().getName(); final Metric dropTotal; final Metric dropRate; - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - dropTotal = metrics.get(new MetricName( - "expired-window-record-drop-total", - "stream-" + metricScope + "-metrics", - "The total number of dropped records due to an expired window", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry(metricScope + "-state-id", sessionStore.name()) - ) - )); - - dropRate = metrics.get(new MetricName( - "expired-window-record-drop-rate", - "stream-" + metricScope + "-metrics", - "The average number of dropped records due to an expired window per second", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry(metricScope + "-state-id", sessionStore.name()) - ) - )); - } else { - dropTotal = metrics.get(new MetricName( - "dropped-records-total", - "stream-task-metrics", - "", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - )); - - dropRate = metrics.get(new MetricName( - "dropped-records-rate", - "stream-task-metrics", - "", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - )); - } + dropTotal = metrics.get(new MetricName( + "dropped-records-total", + "stream-task-metrics", + "", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + )); + + dropRate = metrics.get(new MetricName( + "dropped-records-rate", + "stream-task-metrics", + "", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + )); assertEquals(1.0, dropTotal.metricValue()); assertNotEquals(0.0, dropRate.metricValue()); diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractWindowBytesStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractWindowBytesStoreTest.java index 08fcb6dc114d9..25fbc2f3ec91a 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractWindowBytesStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/AbstractWindowBytesStoreTest.java @@ -92,16 +92,12 @@ abstract WindowStore buildWindowStore(final long retentionPeriod, final Serde keySerde, final Serde valueSerde); - abstract String getMetricsScope(); - - abstract void setClassLoggerToDebug(); - @Before public void setup() { windowStore = buildWindowStore(RETENTION_PERIOD, WINDOW_SIZE, false, Serdes.Integer(), Serdes.String()); recordCollector = new MockRecordCollector(); - context = new InternalMockProcessorContext( + context = new InternalMockProcessorContext<>( baseDir, Serdes.String(), Serdes.Integer(), @@ -1018,18 +1014,8 @@ public void shouldNotThrowInvalidRangeExceptionWithNegativeFromKey() { } @Test - public void shouldLogAndMeasureExpiredRecordsWithBuiltInMetricsVersionLatest() { - shouldLogAndMeasureExpiredRecords(StreamsConfig.METRICS_LATEST); - } - - @Test - public void shouldLogAndMeasureExpiredRecordsWithBuiltInMetricsVersion0100To24() { - shouldLogAndMeasureExpiredRecords(StreamsConfig.METRICS_0100_TO_24); - } - - private void shouldLogAndMeasureExpiredRecords(final String builtInMetricsVersion) { + public void shouldLogAndMeasureExpiredRecords() { final Properties streamsConfig = StreamsTestUtils.getStreamsConfig(); - streamsConfig.setProperty(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG, builtInMetricsVersion); final WindowStore windowStore = buildWindowStore(RETENTION_PERIOD, WINDOW_SIZE, false, Serdes.Integer(), Serdes.String()); final InternalMockProcessorContext context = new InternalMockProcessorContext( @@ -1056,53 +1042,28 @@ private void shouldLogAndMeasureExpiredRecords(final String builtInMetricsVersio final Map metrics = context.metrics().metrics(); - final String metricScope = getMetricsScope(); final String threadId = Thread.currentThread().getName(); final Metric dropTotal; final Metric dropRate; - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - dropTotal = metrics.get(new MetricName( - "expired-window-record-drop-total", - "stream-" + metricScope + "-metrics", - "The total number of dropped records due to an expired window", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry(metricScope + "-state-id", windowStore.name()) - ) - )); - - dropRate = metrics.get(new MetricName( - "expired-window-record-drop-rate", - "stream-" + metricScope + "-metrics", - "The average number of dropped records due to an expired window per second", - mkMap( - mkEntry("client-id", threadId), - mkEntry("task-id", "0_0"), - mkEntry(metricScope + "-state-id", windowStore.name()) - ) - )); - } else { - dropTotal = metrics.get(new MetricName( - "dropped-records-total", - "stream-task-metrics", - "", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - )); - - dropRate = metrics.get(new MetricName( - "dropped-records-rate", - "stream-task-metrics", - "", - mkMap( - mkEntry("thread-id", threadId), - mkEntry("task-id", "0_0") - ) - )); - } + dropTotal = metrics.get(new MetricName( + "dropped-records-total", + "stream-task-metrics", + "", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + )); + + dropRate = metrics.get(new MetricName( + "dropped-records-rate", + "stream-task-metrics", + "", + mkMap( + mkEntry("thread-id", threadId), + mkEntry("task-id", "0_0") + ) + )); assertEquals(1.0, dropTotal.metricValue()); assertNotEquals(0.0, dropRate.metricValue()); diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/CacheFlushListenerStub.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/CacheFlushListenerStub.java index ea4b147f00fa3..b2147390f9fb2 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/CacheFlushListenerStub.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/CacheFlushListenerStub.java @@ -18,6 +18,7 @@ import org.apache.kafka.common.serialization.Deserializer; import org.apache.kafka.streams.kstream.internals.Change; +import org.apache.kafka.streams.processor.api.Record; import java.util.HashMap; import java.util.Map; @@ -46,4 +47,15 @@ public void apply(final byte[] key, ) ); } + + @Override + public void apply(final Record> record) { + forwarded.put( + keyDeserializer.deserialize(null, record.key()), + new Change<>( + valueDeserializer.deserialize(null, record.value().newValue), + valueDeserializer.deserialize(null, record.value().oldValue) + ) + ); + } } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingInMemoryKeyValueStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingInMemoryKeyValueStoreTest.java index 66b13c1ad434f..ff78642f80d47 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingInMemoryKeyValueStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingInMemoryKeyValueStoreTest.java @@ -74,7 +74,7 @@ public void setUp() { store = new CachingKeyValueStore(underlyingStore); store.setFlushListener(cacheFlushListener, false); cache = new ThreadCache(new LogContext("testCache "), maxCacheSizeBytes, new MockStreamsMetrics(new Metrics())); - context = new InternalMockProcessorContext(null, null, null, null, cache); + context = new InternalMockProcessorContext<>(null, null, null, null, cache); context.setRecordContext(new ProcessorRecordContext(10, 0, 0, TOPIC, null)); store.init((StateStoreContext) context, null); } @@ -200,7 +200,7 @@ private void setUpCloseTests() { EasyMock.replay(underlyingStore); store = new CachingKeyValueStore(underlyingStore); cache = EasyMock.niceMock(ThreadCache.class); - context = new InternalMockProcessorContext(TestUtils.tempDirectory(), null, null, null, cache); + context = new InternalMockProcessorContext<>(TestUtils.tempDirectory(), null, null, null, cache); context.setRecordContext(new ProcessorRecordContext(10, 0, 0, TOPIC, null)); store.init((StateStoreContext) context, store); } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingInMemorySessionStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingInMemorySessionStoreTest.java index e584e2ca706b6..417b35f9dd9f0 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingInMemorySessionStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingInMemorySessionStoreTest.java @@ -31,6 +31,7 @@ import org.apache.kafka.streams.kstream.internals.SessionWindow; import org.apache.kafka.streams.processor.ProcessorContext; import org.apache.kafka.streams.processor.StateStoreContext; +import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.processor.internals.MockStreamsMetrics; import org.apache.kafka.streams.processor.internals.ProcessorRecordContext; import org.apache.kafka.streams.processor.internals.testutil.LogCaptureAppender; @@ -87,7 +88,7 @@ public void before() { underlyingStore = new InMemorySessionStore("store-name", Long.MAX_VALUE, "metric-scope"); cachingStore = new CachingSessionStore(underlyingStore, SEGMENT_INTERVAL); cache = new ThreadCache(new LogContext("testCache "), MAX_CACHE_SIZE_BYTES, new MockStreamsMetrics(new Metrics())); - context = new InternalMockProcessorContext(TestUtils.tempDirectory(), null, null, null, cache); + context = new InternalMockProcessorContext<>(TestUtils.tempDirectory(), null, null, null, cache); context.setRecordContext(new ProcessorRecordContext(DEFAULT_TIMESTAMP, 0, 0, TOPIC, null)); cachingStore.init((StateStoreContext) context, cachingStore); } @@ -223,7 +224,7 @@ private void setUpCloseTests() { EasyMock.replay(underlyingStore); cachingStore = new CachingSessionStore(underlyingStore, SEGMENT_INTERVAL); cache = EasyMock.niceMock(ThreadCache.class); - final InternalMockProcessorContext context = new InternalMockProcessorContext(TestUtils.tempDirectory(), null, null, null, cache); + final InternalMockProcessorContext context = new InternalMockProcessorContext<>(TestUtils.tempDirectory(), null, null, null, cache); context.setRecordContext(new ProcessorRecordContext(10, 0, 0, TOPIC, null)); cachingStore.init((StateStoreContext) context, cachingStore); } @@ -752,5 +753,18 @@ public void apply(final byte[] key, valueDesializer.deserialize(null, oldValue)), timestamp)); } + + @Override + public void apply(final Record> record) { + forwarded.add( + new KeyValueTimestamp<>( + keyDeserializer.deserialize(null, record.key()), + new Change<>( + valueDesializer.deserialize(null, record.value().newValue), + valueDesializer.deserialize(null, record.value().oldValue)), + record.timestamp() + ) + ); + } } } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingPersistentSessionStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingPersistentSessionStoreTest.java index 7f8a394c278c0..55018bf39edc7 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingPersistentSessionStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingPersistentSessionStoreTest.java @@ -30,6 +30,7 @@ import org.apache.kafka.streams.kstream.internals.Change; import org.apache.kafka.streams.kstream.internals.SessionWindow; import org.apache.kafka.streams.processor.StateStoreContext; +import org.apache.kafka.streams.processor.api.Record; import org.apache.kafka.streams.processor.internals.MockStreamsMetrics; import org.apache.kafka.streams.processor.internals.ProcessorRecordContext; import org.apache.kafka.streams.processor.internals.testutil.LogCaptureAppender; @@ -92,7 +93,7 @@ public void before() { cachingStore = new CachingSessionStore(underlyingStore, SEGMENT_INTERVAL); cache = new ThreadCache(new LogContext("testCache "), MAX_CACHE_SIZE_BYTES, new MockStreamsMetrics(new Metrics())); final InternalMockProcessorContext context = - new InternalMockProcessorContext(TestUtils.tempDirectory(), null, null, null, cache); + new InternalMockProcessorContext<>(TestUtils.tempDirectory(), null, null, null, cache); context.setRecordContext(new ProcessorRecordContext(DEFAULT_TIMESTAMP, 0, 0, TOPIC, null)); cachingStore.init((StateStoreContext) context, cachingStore); } @@ -209,7 +210,7 @@ private void setUpCloseTests() { cachingStore = new CachingSessionStore(underlyingStore, SEGMENT_INTERVAL); cache = EasyMock.niceMock(ThreadCache.class); final InternalMockProcessorContext context = - new InternalMockProcessorContext(TestUtils.tempDirectory(), null, null, null, cache); + new InternalMockProcessorContext<>(TestUtils.tempDirectory(), null, null, null, cache); context.setRecordContext(new ProcessorRecordContext(10, 0, 0, TOPIC, null)); cachingStore.init((StateStoreContext) context, cachingStore); } @@ -763,5 +764,18 @@ public void apply(final byte[] key, ) ); } + + @Override + public void apply(final Record> record) { + forwarded.add( + new KeyValueTimestamp<>( + keyDeserializer.deserialize(null, record.key()), + new Change<>( + valueDesializer.deserialize(null, record.value().newValue), + valueDesializer.deserialize(null, record.value().oldValue)), + record.timestamp() + ) + ); + } } } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingPersistentWindowStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingPersistentWindowStoreTest.java index 26b5e417f3284..ef9345b849e36 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingPersistentWindowStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/CachingPersistentWindowStoreTest.java @@ -103,7 +103,7 @@ public void setUp() { cachingStore = new CachingWindowStore(underlyingStore, WINDOW_SIZE, SEGMENT_INTERVAL); cachingStore.setFlushListener(cacheListener, false); cache = new ThreadCache(new LogContext("testCache "), MAX_CACHE_SIZE_BYTES, new MockStreamsMetrics(new Metrics())); - context = new InternalMockProcessorContext(TestUtils.tempDirectory(), null, null, null, cache); + context = new InternalMockProcessorContext<>(TestUtils.tempDirectory(), null, null, null, cache); context.setRecordContext(new ProcessorRecordContext(DEFAULT_TIMESTAMP, 0, 0, TOPIC, null)); cachingStore.init((StateStoreContext) context, cachingStore); } @@ -906,7 +906,7 @@ private void setUpCloseTests() { EasyMock.replay(underlyingStore); cachingStore = new CachingWindowStore(underlyingStore, WINDOW_SIZE, SEGMENT_INTERVAL); cache = EasyMock.createNiceMock(ThreadCache.class); - context = new InternalMockProcessorContext(TestUtils.tempDirectory(), null, null, null, cache); + context = new InternalMockProcessorContext<>(TestUtils.tempDirectory(), null, null, null, cache); context.setRecordContext(new ProcessorRecordContext(10, 0, 0, TOPIC, null)); cachingStore.init((StateStoreContext) context, cachingStore); } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/ChangeLoggingKeyValueBytesStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/ChangeLoggingKeyValueBytesStoreTest.java index 255994ca33b44..5f524faacc852 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/ChangeLoggingKeyValueBytesStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/ChangeLoggingKeyValueBytesStoreTest.java @@ -46,6 +46,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +@SuppressWarnings("rawtypes") public class ChangeLoggingKeyValueBytesStoreTest { private final MockRecordCollector collector = new MockRecordCollector(); @@ -64,7 +65,7 @@ public void before() { } private InternalMockProcessorContext mockContext() { - return new InternalMockProcessorContext( + return new InternalMockProcessorContext<>( TestUtils.tempDirectory(), Serdes.String(), Serdes.Long(), diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/ChangeLoggingTimestampedKeyValueBytesStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/ChangeLoggingTimestampedKeyValueBytesStoreTest.java index 8295f7d2e4af8..d65d948c40bdb 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/ChangeLoggingTimestampedKeyValueBytesStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/ChangeLoggingTimestampedKeyValueBytesStoreTest.java @@ -42,6 +42,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +@SuppressWarnings("rawtypes") public class ChangeLoggingTimestampedKeyValueBytesStoreTest { private final MockRecordCollector collector = new MockRecordCollector(); @@ -64,7 +65,7 @@ public void before() { } private InternalMockProcessorContext mockContext() { - return new InternalMockProcessorContext( + return new InternalMockProcessorContext<>( TestUtils.tempDirectory(), Serdes.String(), Serdes.Long(), diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/CompositeReadOnlyKeyValueStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/CompositeReadOnlyKeyValueStoreTest.java index 736721ac48bdb..63cc61ea54759 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/CompositeReadOnlyKeyValueStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/CompositeReadOnlyKeyValueStoreTest.java @@ -76,8 +76,15 @@ private KeyValueStore newStoreInstance() { Serdes.String()) .build(); - final InternalMockProcessorContext context = new InternalMockProcessorContext(new StateSerdes<>(ProcessorStateManager.storeChangelogTopic("appId", storeName), - Serdes.String(), Serdes.String()), new MockRecordCollector()); + @SuppressWarnings("rawtypes") final InternalMockProcessorContext context = + new InternalMockProcessorContext<>( + new StateSerdes<>( + ProcessorStateManager.storeChangelogTopic("appId", storeName, null), + Serdes.String(), + Serdes.String() + ), + new MockRecordCollector() + ); context.setTime(1L); store.init((StateStoreContext) context, store); diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryKeyValueStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryKeyValueStoreTest.java index 831e6841edbf0..87a206371a0d6 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryKeyValueStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryKeyValueStoreTest.java @@ -80,6 +80,7 @@ protected KeyValueStore createKeyValueStore(final StateStoreContext return store; } + @SuppressWarnings("unchecked") @Test public void shouldRemoveKeysWithNullValues() { store.close(); diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryLRUCacheStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryLRUCacheStoreTest.java index a044eda442bb2..53057b9c1e219 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryLRUCacheStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryLRUCacheStoreTest.java @@ -132,6 +132,7 @@ public void testEvict() { assertEquals(3, driver.numFlushedEntryRemoved()); } + @SuppressWarnings("unchecked") @Test public void testRestoreEvict() { store.close(); diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemorySessionStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemorySessionStoreTest.java index e517e9d6bd7e9..a6ea78078beaa 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemorySessionStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemorySessionStoreTest.java @@ -48,11 +48,6 @@ SessionStore buildSessionStore(final long retentionPeriod, valueSerde).build(); } - @Override - String getMetricsScope() { - return new InMemorySessionBytesStoreSupplier(null, 0).metricsScope(); - } - @Test public void shouldRemoveExpired() { sessionStore.put(new Windowed<>("a", new SessionWindow(0, 0)), 1L); diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryWindowStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryWindowStoreTest.java index 2ef9bad4aa866..b18e8a7252ed6 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryWindowStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/InMemoryWindowStoreTest.java @@ -20,7 +20,6 @@ import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.streams.KeyValue; import org.apache.kafka.streams.kstream.Windowed; -import org.apache.kafka.streams.processor.internals.testutil.LogCaptureAppender; import org.apache.kafka.streams.state.KeyValueIterator; import org.apache.kafka.streams.state.StateSerdes; import org.apache.kafka.streams.state.Stores; @@ -57,16 +56,7 @@ WindowStore buildWindowStore(final long retentionPeriod, .build(); } - @Override - String getMetricsScope() { - return new InMemoryWindowBytesStoreSupplier(null, 0, 0, false).metricsScope(); - } - - @Override - void setClassLoggerToDebug() { - LogCaptureAppender.setClassLoggerToDebug(InMemoryWindowStore.class); - } - + @SuppressWarnings("unchecked") @Test public void shouldRestore() { // should be empty initially diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/KeyValueSegmentsTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/KeyValueSegmentsTest.java index aeef8ce8e13f5..c8f1a0e061883 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/KeyValueSegmentsTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/KeyValueSegmentsTest.java @@ -55,7 +55,7 @@ public class KeyValueSegmentsTest { @Before public void createContext() { stateDirectory = TestUtils.tempDirectory(); - context = new InternalMockProcessorContext( + context = new InternalMockProcessorContext<>( stateDirectory, Serdes.String(), Serdes.Long(), diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredKeyValueStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredKeyValueStoreTest.java index 095ff87f6f77f..138359073b40f 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredKeyValueStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredKeyValueStoreTest.java @@ -48,13 +48,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -62,7 +56,6 @@ import static org.apache.kafka.common.utils.Utils.mkEntry; import static org.apache.kafka.common.utils.Utils.mkMap; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.ROLLUP_VALUE; import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.aryEq; import static org.easymock.EasyMock.eq; @@ -75,13 +68,13 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -@RunWith(Parameterized.class) public class MeteredKeyValueStoreTest { @Rule @@ -90,10 +83,8 @@ public class MeteredKeyValueStoreTest { private static final String APPLICATION_ID = "test-app"; private static final String STORE_NAME = "store-name"; private static final String STORE_TYPE = "scope"; - private static final String STORE_LEVEL_GROUP_FROM_0100_TO_24 = "stream-" + STORE_TYPE + "-state-metrics"; private static final String STORE_LEVEL_GROUP = "stream-state-metrics"; private static final String CHANGELOG_TOPIC = "changelog-topic"; - private static final String THREAD_ID_TAG_KEY_FROM_0100_TO_24 = "client-id"; private static final String THREAD_ID_TAG_KEY = "thread-id"; private static final String KEY = "key"; private static final Bytes KEY_BYTES = Bytes.wrap(KEY.getBytes()); @@ -102,7 +93,7 @@ public class MeteredKeyValueStoreTest { private static final KeyValue BYTE_KEY_VALUE_PAIR = KeyValue.pair(KEY_BYTES, VALUE_BYTES); private final String threadId = Thread.currentThread().getName(); - private final TaskId taskId = new TaskId(0, 0); + private final TaskId taskId = new TaskId(0, 0, "My-Topology"); @Mock(type = MockType.NICE) private KeyValueStore inner; @@ -111,21 +102,8 @@ public class MeteredKeyValueStoreTest { private MeteredKeyValueStore metered; private final Metrics metrics = new Metrics(); - private String storeLevelGroup; - private String threadIdTagKey; private Map tags; - @Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][] { - {StreamsConfig.METRICS_LATEST}, - {StreamsConfig.METRICS_0100_TO_24} - }); - } - - @Parameter - public String builtInMetricsVersion; - @Before public void before() { final Time mockTime = new MockTime(); @@ -139,17 +117,13 @@ public void before() { metrics.config().recordLevel(Sensor.RecordingLevel.DEBUG); expect(context.applicationId()).andStubReturn(APPLICATION_ID); expect(context.metrics()).andStubReturn( - new StreamsMetricsImpl(metrics, "test", builtInMetricsVersion, mockTime) + new StreamsMetricsImpl(metrics, "test", StreamsConfig.METRICS_LATEST, mockTime) ); expect(context.taskId()).andStubReturn(taskId); expect(context.changelogFor(STORE_NAME)).andStubReturn(CHANGELOG_TOPIC); expect(inner.name()).andStubReturn(STORE_NAME); - storeLevelGroup = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? STORE_LEVEL_GROUP_FROM_0100_TO_24 : STORE_LEVEL_GROUP; - threadIdTagKey = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? THREAD_ID_TAG_KEY_FROM_0100_TO_24 : THREAD_ID_TAG_KEY; tags = mkMap( - mkEntry(threadIdTagKey, threadId), + mkEntry(THREAD_ID_TAG_KEY, threadId), mkEntry("task-id", taskId.toString()), mkEntry(STORE_TYPE + "-state-id", STORE_NAME) ); @@ -204,7 +178,7 @@ public void shouldPassChangelogTopicNameToStateStoreSerde() { @Test public void shouldPassDefaultChangelogTopicNameToStateStoreSerdeIfLoggingDisabled() { - final String defaultChangelogTopicName = ProcessorStateManager.storeChangelogTopic(APPLICATION_ID, STORE_NAME); + final String defaultChangelogTopicName = ProcessorStateManager.storeChangelogTopic(APPLICATION_ID, STORE_NAME, taskId.namedTopology()); expect(context.changelogFor(STORE_NAME)).andReturn(null); doShouldPassChangelogTopicNameToStateStoreSerde(defaultChangelogTopicName); } @@ -248,24 +222,26 @@ public void testMetrics() { metrics.addReporter(reporter); assertTrue(reporter.containsMbean(String.format( "kafka.streams:type=%s,%s=%s,task-id=%s,%s-state-id=%s", - storeLevelGroup, - threadIdTagKey, + STORE_LEVEL_GROUP, + THREAD_ID_TAG_KEY, threadId, taskId.toString(), STORE_TYPE, STORE_NAME ))); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertTrue(reporter.containsMbean(String.format( - "kafka.streams:type=%s,%s=%s,task-id=%s,%s-state-id=%s", - storeLevelGroup, - threadIdTagKey, - threadId, - taskId.toString(), - STORE_TYPE, - ROLLUP_VALUE - ))); - } + } + + @Test + public void shouldRecordRestoreLatencyOnInit() { + inner.init((StateStoreContext) context, metered); + + init(); + + // it suffices to verify one restore metric since all restore metrics are recorded by the same sensor + // and the sensor is tested elsewhere + final KafkaMetric metric = metric("restore-rate"); + assertThat((Double) metric.metricValue(), greaterThan(0.0)); + verify(inner); } @Test @@ -357,7 +333,7 @@ public void shouldGetAllFromInnerStoreAndRecordAllMetric() { assertFalse(iterator.hasNext()); iterator.close(); - final KafkaMetric metric = metric(new MetricName("all-rate", storeLevelGroup, "", tags)); + final KafkaMetric metric = metric(new MetricName("all-rate", STORE_LEVEL_GROUP, "", tags)); assertTrue((Double) metric.metricValue() > 0); verify(inner); } @@ -507,14 +483,14 @@ private KafkaMetric metric(final MetricName metricName) { } private KafkaMetric metric(final String name) { - return metrics.metric(new MetricName(name, storeLevelGroup, "", tags)); + return metrics.metric(new MetricName(name, STORE_LEVEL_GROUP, "", tags)); } private List storeMetrics() { return metrics.metrics() .keySet() .stream() - .filter(name -> name.group().equals(storeLevelGroup) && name.tags().equals(tags)) + .filter(name -> name.group().equals(STORE_LEVEL_GROUP) && name.tags().equals(tags)) .collect(Collectors.toList()); } } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredSessionStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredSessionStoreTest.java index 07ffed3b31056..8f898d8bec5ca 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredSessionStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredSessionStoreTest.java @@ -50,13 +50,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -64,7 +58,6 @@ import static org.apache.kafka.common.utils.Utils.mkEntry; import static org.apache.kafka.common.utils.Utils.mkMap; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.ROLLUP_VALUE; import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.aryEq; import static org.easymock.EasyMock.eq; @@ -83,7 +76,6 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -@RunWith(Parameterized.class) public class MeteredSessionStoreTest { @Rule @@ -92,9 +84,7 @@ public class MeteredSessionStoreTest { private static final String APPLICATION_ID = "test-app"; private static final String STORE_TYPE = "scope"; private static final String STORE_NAME = "mocked-store"; - private static final String STORE_LEVEL_GROUP_FROM_0100_TO_24 = "stream-" + STORE_TYPE + "-state-metrics"; private static final String STORE_LEVEL_GROUP = "stream-state-metrics"; - private static final String THREAD_ID_TAG_KEY_FROM_0100_TO_24 = "client-id"; private static final String THREAD_ID_TAG_KEY = "thread-id"; private static final String CHANGELOG_TOPIC = "changelog-topic"; private static final String KEY = "key"; @@ -107,7 +97,7 @@ public class MeteredSessionStoreTest { private static final long END_TIMESTAMP = 42L; private final String threadId = Thread.currentThread().getName(); - private final TaskId taskId = new TaskId(0, 0); + private final TaskId taskId = new TaskId(0, 0, "My-Topology"); private final Metrics metrics = new Metrics(); private MeteredSessionStore store; @Mock(type = MockType.NICE) @@ -115,21 +105,8 @@ public class MeteredSessionStoreTest { @Mock(type = MockType.NICE) private InternalProcessorContext context; - private String storeLevelGroup; - private String threadIdTagKey; private Map tags; - - @Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][] { - {StreamsConfig.METRICS_LATEST}, - {StreamsConfig.METRICS_0100_TO_24} - }); - } - - @Parameter - public String builtInMetricsVersion; - + @Before public void before() { final Time mockTime = new MockTime(); @@ -143,16 +120,12 @@ public void before() { metrics.config().recordLevel(Sensor.RecordingLevel.DEBUG); expect(context.applicationId()).andStubReturn(APPLICATION_ID); expect(context.metrics()) - .andStubReturn(new StreamsMetricsImpl(metrics, "test", builtInMetricsVersion, mockTime)); + .andStubReturn(new StreamsMetricsImpl(metrics, "test", StreamsConfig.METRICS_LATEST, mockTime)); expect(context.taskId()).andStubReturn(taskId); expect(context.changelogFor(STORE_NAME)).andStubReturn(CHANGELOG_TOPIC); expect(innerStore.name()).andStubReturn(STORE_NAME); - storeLevelGroup = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? STORE_LEVEL_GROUP_FROM_0100_TO_24 : STORE_LEVEL_GROUP; - threadIdTagKey = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? THREAD_ID_TAG_KEY_FROM_0100_TO_24 : THREAD_ID_TAG_KEY; tags = mkMap( - mkEntry(threadIdTagKey, threadId), + mkEntry(THREAD_ID_TAG_KEY, threadId), mkEntry("task-id", taskId.toString()), mkEntry(STORE_TYPE + "-state-id", STORE_NAME) ); @@ -208,7 +181,7 @@ public void shouldPassChangelogTopicNameToStateStoreSerde() { @Test public void shouldPassDefaultChangelogTopicNameToStateStoreSerdeIfLoggingDisabled() { final String defaultChangelogTopicName = - ProcessorStateManager.storeChangelogTopic(APPLICATION_ID, STORE_NAME); + ProcessorStateManager.storeChangelogTopic(APPLICATION_ID, STORE_NAME, taskId.namedTopology()); expect(context.changelogFor(STORE_NAME)).andReturn(null); doShouldPassChangelogTopicNameToStateStoreSerde(defaultChangelogTopicName); } @@ -252,24 +225,13 @@ public void testMetrics() { metrics.addReporter(reporter); assertTrue(reporter.containsMbean(String.format( "kafka.streams:type=%s,%s=%s,task-id=%s,%s-state-id=%s", - storeLevelGroup, - threadIdTagKey, + STORE_LEVEL_GROUP, + THREAD_ID_TAG_KEY, threadId, taskId.toString(), STORE_TYPE, STORE_NAME ))); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertTrue(reporter.containsMbean(String.format( - "kafka.streams:type=%s,%s=%s,task-id=%s,%s-state-id=%s", - storeLevelGroup, - threadIdTagKey, - threadId, - taskId.toString(), - STORE_TYPE, - ROLLUP_VALUE - ))); - } } @Test @@ -280,6 +242,8 @@ public void shouldWriteBytesToInnerStoreAndRecordPutMetric() { store.put(WINDOWED_KEY, VALUE); + // it suffices to verify one put metric since all put metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("put-rate"); assertTrue(((Double) metric.metricValue()) > 0); verify(innerStore); @@ -297,6 +261,8 @@ public void shouldFindSessionsFromStoreAndRecordFetchMetric() { assertFalse(iterator.hasNext()); iterator.close(); + // it suffices to verify one fetch metric since all put metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("fetch-rate"); assertTrue((Double) metric.metricValue() > 0); verify(innerStore); @@ -317,6 +283,8 @@ public void shouldBackwardFindSessionsFromStoreAndRecordFetchMetric() { assertFalse(iterator.hasNext()); iterator.close(); + // it suffices to verify one fetch metric since all put metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("fetch-rate"); assertTrue((Double) metric.metricValue() > 0); verify(innerStore); @@ -334,6 +302,8 @@ public void shouldFindSessionRangeFromStoreAndRecordFetchMetric() { assertFalse(iterator.hasNext()); iterator.close(); + // it suffices to verify one fetch metric since all put metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("fetch-rate"); assertTrue((Double) metric.metricValue() > 0); verify(innerStore); @@ -354,6 +324,8 @@ public void shouldBackwardFindSessionRangeFromStoreAndRecordFetchMetric() { assertFalse(iterator.hasNext()); iterator.close(); + // it suffices to verify one fetch metric since all put metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("fetch-rate"); assertTrue((Double) metric.metricValue() > 0); verify(innerStore); @@ -368,6 +340,8 @@ public void shouldRemoveFromStoreAndRecordRemoveMetric() { store.remove(new Windowed<>(KEY, new SessionWindow(0, 0))); + // it suffices to verify one remove metric since all remove metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("remove-rate"); assertTrue((Double) metric.metricValue() > 0); verify(innerStore); @@ -385,6 +359,8 @@ public void shouldFetchForKeyAndRecordFetchMetric() { assertFalse(iterator.hasNext()); iterator.close(); + // it suffices to verify one fetch metric since all fetch metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("fetch-rate"); assertTrue((Double) metric.metricValue() > 0); verify(innerStore); @@ -405,6 +381,8 @@ public void shouldBackwardFetchForKeyAndRecordFetchMetric() { assertFalse(iterator.hasNext()); iterator.close(); + // it suffices to verify one fetch metric since all fetch metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("fetch-rate"); assertTrue((Double) metric.metricValue() > 0); verify(innerStore); @@ -422,6 +400,8 @@ public void shouldFetchRangeFromStoreAndRecordFetchMetric() { assertFalse(iterator.hasNext()); iterator.close(); + // it suffices to verify one fetch metric since all fetch metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("fetch-rate"); assertTrue((Double) metric.metricValue() > 0); verify(innerStore); @@ -442,6 +422,8 @@ public void shouldBackwardFetchRangeFromStoreAndRecordFetchMetric() { assertFalse(iterator.hasNext()); iterator.close(); + // it suffices to verify one fetch metric since all fetch metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("fetch-rate"); assertTrue((Double) metric.metricValue() > 0); verify(innerStore); @@ -450,6 +432,9 @@ public void shouldBackwardFetchRangeFromStoreAndRecordFetchMetric() { @Test public void shouldRecordRestoreTimeOnInit() { init(); + + // it suffices to verify one restore metric since all restore metrics are recorded by the same sensor + // and the sensor is tested elsewhere final KafkaMetric metric = metric("restore-rate"); assertTrue((Double) metric.metricValue() > 0); } @@ -609,14 +594,14 @@ public void shouldRemoveMetricsEvenIfWrappedStoreThrowsOnClose() { } private KafkaMetric metric(final String name) { - return this.metrics.metric(new MetricName(name, storeLevelGroup, "", this.tags)); + return this.metrics.metric(new MetricName(name, STORE_LEVEL_GROUP, "", this.tags)); } private List storeMetrics() { return metrics.metrics() .keySet() .stream() - .filter(name -> name.group().equals(storeLevelGroup) && name.tags().equals(tags)) + .filter(name -> name.group().equals(STORE_LEVEL_GROUP) && name.tags().equals(tags)) .collect(Collectors.toList()); } } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredTimestampedKeyValueStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredTimestampedKeyValueStoreTest.java index c0ef7c61f627d..0a7eb9a2d1a6f 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredTimestampedKeyValueStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredTimestampedKeyValueStoreTest.java @@ -50,20 +50,13 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -import java.util.Arrays; -import java.util.Collection; + import java.util.Collections; import java.util.List; import java.util.Map; import static org.apache.kafka.common.utils.Utils.mkEntry; import static org.apache.kafka.common.utils.Utils.mkMap; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.ROLLUP_VALUE; import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.aryEq; import static org.easymock.EasyMock.eq; @@ -80,7 +73,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -@RunWith(Parameterized.class) public class MeteredTimestampedKeyValueStoreTest { @Rule @@ -89,10 +81,8 @@ public class MeteredTimestampedKeyValueStoreTest { private static final String APPLICATION_ID = "test-app"; private static final String STORE_NAME = "store-name"; private static final String STORE_TYPE = "scope"; - private static final String STORE_LEVEL_GROUP_FROM_0100_TO_24 = "stream-" + STORE_TYPE + "-state-metrics"; private static final String STORE_LEVEL_GROUP = "stream-state-metrics"; private static final String CHANGELOG_TOPIC = "changelog-topic-name"; - private static final String THREAD_ID_TAG_KEY_FROM_0100_TO_24 = "client-id"; private static final String THREAD_ID_TAG_KEY = "thread-id"; private static final String KEY = "key"; private static final Bytes KEY_BYTES = Bytes.wrap(KEY.getBytes()); @@ -103,7 +93,7 @@ public class MeteredTimestampedKeyValueStoreTest { private final String threadId = Thread.currentThread().getName(); - private final TaskId taskId = new TaskId(0, 0); + private final TaskId taskId = new TaskId(0, 0, "My-Topology"); @Mock(type = MockType.NICE) private KeyValueStore inner; @Mock(type = MockType.NICE) @@ -114,21 +104,8 @@ public class MeteredTimestampedKeyValueStoreTest { VALUE_AND_TIMESTAMP_BYTES ); private final Metrics metrics = new Metrics(); - private String storeLevelGroup; - private String threadIdTagKey; private Map tags; - @Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][] { - {StreamsConfig.METRICS_LATEST}, - {StreamsConfig.METRICS_0100_TO_24} - }); - } - - @Parameter - public String builtInMetricsVersion; - @Before public void before() { final Time mockTime = new MockTime(); @@ -142,17 +119,13 @@ public void before() { metrics.config().recordLevel(Sensor.RecordingLevel.DEBUG); expect(context.applicationId()).andStubReturn(APPLICATION_ID); expect(context.metrics()) - .andStubReturn(new StreamsMetricsImpl(metrics, "test", builtInMetricsVersion, mockTime)); + .andStubReturn(new StreamsMetricsImpl(metrics, "test", StreamsConfig.METRICS_LATEST, mockTime)); expect(context.taskId()).andStubReturn(taskId); expect(context.changelogFor(STORE_NAME)).andStubReturn(CHANGELOG_TOPIC); expectSerdes(); expect(inner.name()).andStubReturn(STORE_NAME); - storeLevelGroup = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? STORE_LEVEL_GROUP_FROM_0100_TO_24 : STORE_LEVEL_GROUP; - threadIdTagKey = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? THREAD_ID_TAG_KEY_FROM_0100_TO_24 : THREAD_ID_TAG_KEY; tags = mkMap( - mkEntry(threadIdTagKey, threadId), + mkEntry(THREAD_ID_TAG_KEY, threadId), mkEntry("task-id", taskId.toString()), mkEntry(STORE_TYPE + "-state-id", STORE_NAME) ); @@ -214,7 +187,7 @@ public void shouldPassChangelogTopicNameToStateStoreSerde() { @Test public void shouldPassDefaultChangelogTopicNameToStateStoreSerdeIfLoggingDisabled() { - final String defaultChangelogTopicName = ProcessorStateManager.storeChangelogTopic(APPLICATION_ID, STORE_NAME); + final String defaultChangelogTopicName = ProcessorStateManager.storeChangelogTopic(APPLICATION_ID, STORE_NAME, taskId.namedTopology()); expect(context.changelogFor(STORE_NAME)).andReturn(null); doShouldPassChangelogTopicNameToStateStoreSerde(defaultChangelogTopicName); } @@ -258,24 +231,13 @@ public void testMetrics() { metrics.addReporter(reporter); assertTrue(reporter.containsMbean(String.format( "kafka.streams:type=%s,%s=%s,task-id=%s,%s-state-id=%s", - storeLevelGroup, - threadIdTagKey, + STORE_LEVEL_GROUP, + THREAD_ID_TAG_KEY, threadId, taskId.toString(), STORE_TYPE, STORE_NAME ))); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertTrue(reporter.containsMbean(String.format( - "kafka.streams:type=%s,%s=%s,task-id=%s,%s-state-id=%s", - storeLevelGroup, - threadIdTagKey, - threadId, - taskId.toString(), - STORE_TYPE, - ROLLUP_VALUE - ))); - } } @Test public void shouldWriteBytesToInnerStoreAndRecordPutMetric() { @@ -359,7 +321,7 @@ public void shouldPutIfAbsentAndRecordPutIfAbsentMetric() { } private KafkaMetric metric(final String name) { - return this.metrics.metric(new MetricName(name, storeLevelGroup, "", tags)); + return this.metrics.metric(new MetricName(name, STORE_LEVEL_GROUP, "", tags)); } @SuppressWarnings("unchecked") @@ -415,7 +377,7 @@ public void shouldGetAllFromInnerStoreAndRecordAllMetric() { assertFalse(iterator.hasNext()); iterator.close(); - final KafkaMetric metric = metric(new MetricName("all-rate", storeLevelGroup, "", tags)); + final KafkaMetric metric = metric(new MetricName("all-rate", STORE_LEVEL_GROUP, "", tags)); assertTrue((Double) metric.metricValue() > 0); verify(inner); } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredTimestampedWindowStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredTimestampedWindowStoreTest.java index c05c1ba5e67c3..bc606919339d4 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredTimestampedWindowStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredTimestampedWindowStoreTest.java @@ -31,6 +31,7 @@ import org.apache.kafka.streams.errors.StreamsException; import org.apache.kafka.streams.processor.ProcessorContext; import org.apache.kafka.streams.processor.StateStoreContext; +import org.apache.kafka.streams.processor.TaskId; import org.apache.kafka.streams.processor.internals.ProcessorStateManager; import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl; import org.apache.kafka.streams.state.ValueAndTimestamp; @@ -67,6 +68,7 @@ public class MeteredTimestampedWindowStoreTest { private static final int WINDOW_SIZE_MS = 10; private InternalMockProcessorContext context; + private final TaskId taskId = new TaskId(0, 0, "My-Topology"); private final WindowStore innerStoreMock = EasyMock.createNiceMock(WindowStore.class); private final Metrics metrics = new Metrics(new MetricConfig().recordLevel(Sensor.RecordingLevel.DEBUG)); private MeteredTimestampedWindowStore store = new MeteredTimestampedWindowStore<>( @@ -87,7 +89,7 @@ public void setUp() { final StreamsMetricsImpl streamsMetrics = new StreamsMetricsImpl(metrics, "test", StreamsConfig.METRICS_LATEST, new MockTime()); - context = new InternalMockProcessorContext( + context = new InternalMockProcessorContext<>( TestUtils.tempDirectory(), Serdes.String(), Serdes.Long(), @@ -95,7 +97,8 @@ public void setUp() { new StreamsConfig(StreamsTestUtils.getStreamsConfig()), MockRecordCollector::new, new ThreadCache(new LogContext("testCache "), 0, streamsMetrics), - Time.SYSTEM + Time.SYSTEM, + taskId ); } @@ -147,7 +150,7 @@ public void shouldPassChangelogTopicNameToStateStoreSerde() { @Test public void shouldPassDefaultChangelogTopicNameToStateStoreSerdeIfLoggingDisabled() { final String defaultChangelogTopicName = - ProcessorStateManager.storeChangelogTopic(context.applicationId(), STORE_NAME); + ProcessorStateManager.storeChangelogTopic(context.applicationId(), STORE_NAME, taskId.namedTopology()); doShouldPassChangelogTopicNameToStateStoreSerde(defaultChangelogTopicName); } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredWindowStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredWindowStoreTest.java index 538c8d3173a69..41676c9b98c75 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredWindowStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/MeteredWindowStoreTest.java @@ -16,9 +16,9 @@ */ package org.apache.kafka.streams.state.internals; -import org.apache.kafka.common.Metric; import org.apache.kafka.common.MetricName; import org.apache.kafka.common.metrics.JmxReporter; +import org.apache.kafka.common.metrics.KafkaMetric; import org.apache.kafka.common.metrics.KafkaMetricsContext; import org.apache.kafka.common.metrics.MetricConfig; import org.apache.kafka.common.metrics.Metrics; @@ -33,7 +33,6 @@ import org.apache.kafka.common.utils.MockTime; import org.apache.kafka.common.utils.Time; import org.apache.kafka.streams.StreamsConfig; -import org.apache.kafka.streams.kstream.Windowed; import org.apache.kafka.streams.processor.ProcessorContext; import org.apache.kafka.streams.processor.StateStoreContext; import org.apache.kafka.streams.processor.internals.ProcessorStateManager; @@ -45,23 +44,14 @@ import org.apache.kafka.test.TestUtils; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; -import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static java.time.Instant.ofEpochMilli; -import static java.util.Collections.singletonMap; import static org.apache.kafka.common.utils.Utils.mkEntry; import static org.apache.kafka.common.utils.Utils.mkMap; -import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.ROLLUP_VALUE; -import static org.apache.kafka.test.StreamsTestUtils.getMetricByNameFilterByTags; import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.eq; @@ -73,20 +63,17 @@ import static org.easymock.EasyMock.verify; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -@RunWith(Parameterized.class) public class MeteredWindowStoreTest { private static final String STORE_TYPE = "scope"; - private static final String STORE_LEVEL_GROUP_FROM_0100_TO_24 = "stream-" + STORE_TYPE + "-state-metrics"; private static final String STORE_LEVEL_GROUP = "stream-state-metrics"; - private static final String THREAD_ID_TAG_KEY_FROM_0100_TO_24 = "client-id"; private static final String THREAD_ID_TAG_KEY = "thread-id"; private static final String STORE_NAME = "mocked-store"; private static final String CHANGELOG_TOPIC = "changelog-topic"; @@ -109,30 +96,17 @@ public class MeteredWindowStoreTest { new SerdeThatDoesntHandleNull() ); private final Metrics metrics = new Metrics(new MetricConfig().recordLevel(Sensor.RecordingLevel.DEBUG)); - private String storeLevelGroup; - private String threadIdTagKey; private Map tags; { expect(innerStoreMock.name()).andReturn(STORE_NAME).anyTimes(); } - @Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][] { - {StreamsConfig.METRICS_LATEST}, - {StreamsConfig.METRICS_0100_TO_24} - }); - } - - @Parameter - public String builtInMetricsVersion; - @Before public void setUp() { final StreamsMetricsImpl streamsMetrics = - new StreamsMetricsImpl(metrics, "test", builtInMetricsVersion, new MockTime()); - context = new InternalMockProcessorContext( + new StreamsMetricsImpl(metrics, "test", StreamsConfig.METRICS_LATEST, new MockTime()); + context = new InternalMockProcessorContext<>( TestUtils.tempDirectory(), Serdes.String(), Serdes.Long(), @@ -142,12 +116,8 @@ public void setUp() { new ThreadCache(new LogContext("testCache "), 0, streamsMetrics), Time.SYSTEM ); - storeLevelGroup = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? STORE_LEVEL_GROUP_FROM_0100_TO_24 : STORE_LEVEL_GROUP; - threadIdTagKey = - StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion) ? THREAD_ID_TAG_KEY_FROM_0100_TO_24 : THREAD_ID_TAG_KEY; tags = mkMap( - mkEntry(threadIdTagKey, threadId), + mkEntry(THREAD_ID_TAG_KEY, threadId), mkEntry("task-id", context.taskId().toString()), mkEntry(STORE_TYPE + "-state-id", STORE_NAME) ); @@ -201,7 +171,7 @@ public void shouldPassChangelogTopicNameToStateStoreSerde() { @Test public void shouldPassDefaultChangelogTopicNameToStateStoreSerdeIfLoggingDisabled() { final String defaultChangelogTopicName = - ProcessorStateManager.storeChangelogTopic(context.applicationId(), STORE_NAME); + ProcessorStateManager.storeChangelogTopic(context.applicationId(), STORE_NAME, context.taskId().namedTopology()); doShouldPassChangelogTopicNameToStateStoreSerde(defaultChangelogTopicName); } @@ -246,149 +216,150 @@ public void testMetrics() { metrics.addReporter(reporter); assertTrue(reporter.containsMbean(String.format( "kafka.streams:type=%s,%s=%s,task-id=%s,%s-state-id=%s", - storeLevelGroup, - threadIdTagKey, + STORE_LEVEL_GROUP, + THREAD_ID_TAG_KEY, threadId, context.taskId().toString(), STORE_TYPE, STORE_NAME ))); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertTrue(reporter.containsMbean(String.format( - "kafka.streams:type=%s,%s=%s,task-id=%s,%s-state-id=%s", - storeLevelGroup, - threadIdTagKey, - threadId, - context.taskId().toString(), - STORE_TYPE, - ROLLUP_VALUE - ))); - } } @Test public void shouldRecordRestoreLatencyOnInit() { innerStoreMock.init((StateStoreContext) context, store); - expectLastCall(); replay(innerStoreMock); store.init((StateStoreContext) context, store); - final Map metrics = context.metrics().metrics(); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals(1.0, getMetricByNameFilterByTags( - metrics, - "restore-total", - storeLevelGroup, - singletonMap(STORE_TYPE + "-state-id", STORE_NAME) - ).metricValue()); - assertEquals(1.0, getMetricByNameFilterByTags( - metrics, - "restore-total", - storeLevelGroup, - singletonMap(STORE_TYPE + "-state-id", ROLLUP_VALUE) - ).metricValue()); - } + + // it suffices to verify one restore metric since all restore metrics are recorded by the same sensor + // and the sensor is tested elsewhere + final KafkaMetric metric = metric("restore-rate"); + assertThat((Double) metric.metricValue(), greaterThan(0.0)); + verify(innerStoreMock); } @Test - public void shouldRecordPutLatency() { + public void shouldPutToInnerStoreAndRecordPutMetrics() { final byte[] bytes = "a".getBytes(); innerStoreMock.put(eq(Bytes.wrap(bytes)), anyObject(), eq(context.timestamp())); - expectLastCall(); replay(innerStoreMock); store.init((StateStoreContext) context, store); store.put("a", "a", context.timestamp()); - final Map metrics = context.metrics().metrics(); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals(1.0, getMetricByNameFilterByTags( - metrics, - "put-total", - storeLevelGroup, - singletonMap(STORE_TYPE + "-state-id", STORE_NAME) - ).metricValue()); - assertEquals(1.0, getMetricByNameFilterByTags( - metrics, - "put-total", - storeLevelGroup, - singletonMap(STORE_TYPE + "-state-id", ROLLUP_VALUE) - ).metricValue()); - } + + // it suffices to verify one put metric since all put metrics are recorded by the same sensor + // and the sensor is tested elsewhere + final KafkaMetric metric = metric("put-rate"); + assertThat((Double) metric.metricValue(), greaterThan(0.0)); verify(innerStoreMock); } @Test - public void shouldRecordFetchLatency() { - expect(innerStoreMock.fetch(Bytes.wrap("a".getBytes()), 1, 1)).andReturn(KeyValueIterators.emptyWindowStoreIterator()); + public void shouldFetchFromInnerStoreAndRecordFetchMetrics() { + expect(innerStoreMock.fetch(Bytes.wrap("a".getBytes()), 1, 1)) + .andReturn(KeyValueIterators.emptyWindowStoreIterator()); replay(innerStoreMock); store.init((StateStoreContext) context, store); store.fetch("a", ofEpochMilli(1), ofEpochMilli(1)).close(); // recorded on close; - final Map metrics = context.metrics().metrics(); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals(1.0, getMetricByNameFilterByTags( - metrics, - "fetch-total", - storeLevelGroup, - singletonMap(STORE_TYPE + "-state-id", STORE_NAME) - ).metricValue()); - assertEquals(1.0, getMetricByNameFilterByTags( - metrics, - "fetch-total", - storeLevelGroup, - singletonMap(STORE_TYPE + "-state-id", ROLLUP_VALUE) - ).metricValue()); - } + + // it suffices to verify one fetch metric since all fetch metrics are recorded by the same sensor + // and the sensor is tested elsewhere + final KafkaMetric metric = metric("fetch-rate"); + assertThat((Double) metric.metricValue(), greaterThan(0.0)); verify(innerStoreMock); } @Test - public void shouldRecordFetchRangeLatency() { - expect(innerStoreMock.fetch(Bytes.wrap("a".getBytes()), Bytes.wrap("b".getBytes()), 1, 1)).andReturn(KeyValueIterators., byte[]>emptyIterator()); + public void shouldFetchRangeFromInnerStoreAndRecordFetchMetrics() { + expect(innerStoreMock.fetch(Bytes.wrap("a".getBytes()), Bytes.wrap("b".getBytes()), 1, 1)) + .andReturn(KeyValueIterators.emptyIterator()); replay(innerStoreMock); store.init((StateStoreContext) context, store); store.fetch("a", "b", ofEpochMilli(1), ofEpochMilli(1)).close(); // recorded on close; - final Map metrics = context.metrics().metrics(); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals(1.0, getMetricByNameFilterByTags( - metrics, - "fetch-total", - storeLevelGroup, - singletonMap(STORE_TYPE + "-state-id", STORE_NAME) - ).metricValue()); - assertEquals(1.0, getMetricByNameFilterByTags( - metrics, - "fetch-total", - storeLevelGroup, - singletonMap(STORE_TYPE + "-state-id", ROLLUP_VALUE) - ).metricValue()); - } + + // it suffices to verify one fetch metric since all fetch metrics are recorded by the same sensor + // and the sensor is tested elsewhere + final KafkaMetric metric = metric("fetch-rate"); + assertThat((Double) metric.metricValue(), greaterThan(0.0)); + verify(innerStoreMock); + } + + @Test + public void shouldBackwardFetchFromInnerStoreAndRecordFetchMetrics() { + expect(innerStoreMock.backwardFetch(Bytes.wrap("a".getBytes()), Bytes.wrap("b".getBytes()), 1, 1)) + .andReturn(KeyValueIterators.emptyIterator()); + replay(innerStoreMock); + + store.init((StateStoreContext) context, store); + store.backwardFetch("a", "b", ofEpochMilli(1), ofEpochMilli(1)).close(); // recorded on close; + + // it suffices to verify one fetch metric since all fetch metrics are recorded by the same sensor + // and the sensor is tested elsewhere + final KafkaMetric metric = metric("fetch-rate"); + assertThat((Double) metric.metricValue(), greaterThan(0.0)); + verify(innerStoreMock); + } + + @Test + public void shouldBackwardFetchRangeFromInnerStoreAndRecordFetchMetrics() { + expect(innerStoreMock.backwardFetch(Bytes.wrap("a".getBytes()), Bytes.wrap("b".getBytes()), 1, 1)) + .andReturn(KeyValueIterators.emptyIterator()); + replay(innerStoreMock); + + store.init((StateStoreContext) context, store); + store.backwardFetch("a", "b", ofEpochMilli(1), ofEpochMilli(1)).close(); // recorded on close; + + // it suffices to verify one fetch metric since all fetch metrics are recorded by the same sensor + // and the sensor is tested elsewhere + final KafkaMetric metric = metric("fetch-rate"); + assertThat((Double) metric.metricValue(), greaterThan(0.0)); + verify(innerStoreMock); + } + + @Test + public void shouldFetchAllFromInnerStoreAndRecordFetchMetrics() { + expect(innerStoreMock.fetchAll(1, 1)).andReturn(KeyValueIterators.emptyIterator()); + replay(innerStoreMock); + + store.init((StateStoreContext) context, store); + store.fetchAll(ofEpochMilli(1), ofEpochMilli(1)).close(); // recorded on close; + + // it suffices to verify one fetch metric since all fetch metrics are recorded by the same sensor + // and the sensor is tested elsewhere + final KafkaMetric metric = metric("fetch-rate"); + assertThat((Double) metric.metricValue(), greaterThan(0.0)); + verify(innerStoreMock); + } + + @Test + public void shouldBackwardFetchAllFromInnerStoreAndRecordFetchMetrics() { + expect(innerStoreMock.backwardFetchAll(1, 1)).andReturn(KeyValueIterators.emptyIterator()); + replay(innerStoreMock); + + store.init((StateStoreContext) context, store); + store.backwardFetchAll(ofEpochMilli(1), ofEpochMilli(1)).close(); // recorded on close; + + // it suffices to verify one fetch metric since all fetch metrics are recorded by the same sensor + // and the sensor is tested elsewhere + final KafkaMetric metric = metric("fetch-rate"); + assertThat((Double) metric.metricValue(), greaterThan(0.0)); verify(innerStoreMock); } @Test public void shouldRecordFlushLatency() { innerStoreMock.flush(); - expectLastCall(); replay(innerStoreMock); store.init((StateStoreContext) context, store); store.flush(); - final Map metrics = context.metrics().metrics(); - if (StreamsConfig.METRICS_0100_TO_24.equals(builtInMetricsVersion)) { - assertEquals(1.0, getMetricByNameFilterByTags( - metrics, - "flush-total", - storeLevelGroup, - singletonMap(STORE_TYPE + "-state-id", STORE_NAME) - ).metricValue()); - assertEquals(1.0, getMetricByNameFilterByTags( - metrics, - "flush-total", - storeLevelGroup, - singletonMap(STORE_TYPE + "-state-id", ROLLUP_VALUE) - ).metricValue()); - } + + // it suffices to verify one flush metric since all flush metrics are recorded by the same sensor + // and the sensor is tested elsewhere + final KafkaMetric metric = metric("flush-rate"); + assertTrue((Double) metric.metricValue() > 0); verify(innerStoreMock); } @@ -504,11 +475,15 @@ public void shouldThrowNullPointerOnbackwardFetchRangeIfToIsNull() { assertThrows(NullPointerException.class, () -> store.backwardFetch("from", null, 0L, 1L)); } + private KafkaMetric metric(final String name) { + return metrics.metric(new MetricName(name, STORE_LEVEL_GROUP, "", tags)); + } + private List storeMetrics() { return metrics.metrics() .keySet() .stream() - .filter(name -> name.group().equals(storeLevelGroup) && name.tags().equals(tags)) + .filter(name -> name.group().equals(STORE_LEVEL_GROUP) && name.tags().equals(tags)) .collect(Collectors.toList()); } } diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBSessionStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBSessionStoreTest.java index 436e09f0cfbcc..e3b98b714c9d4 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBSessionStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBSessionStoreTest.java @@ -48,11 +48,6 @@ SessionStore buildSessionStore(final long retentionPeriod, valueSerde).build(); } - @Override - String getMetricsScope() { - return new RocksDbSessionBytesStoreSupplier(null, 0).metricsScope(); - } - @Test public void shouldRemoveExpired() { sessionStore.put(new Windowed<>("a", new SessionWindow(0, 0)), 1L); diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBStoreTest.java index c9e9a8d94692c..14166beed65dd 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBStoreTest.java @@ -87,6 +87,7 @@ import static org.powermock.api.easymock.PowerMock.replay; import static org.powermock.api.easymock.PowerMock.verify; +@SuppressWarnings("unchecked") public class RocksDBStoreTest { private static boolean enableBloomFilters = false; final static String DB_NAME = "db-name"; @@ -107,7 +108,7 @@ public void setUp() { final Properties props = StreamsTestUtils.getStreamsConfig(); props.put(StreamsConfig.ROCKSDB_CONFIG_SETTER_CLASS_CONFIG, MockRocksDbConfigSetter.class); dir = TestUtils.tempDirectory(); - context = new InternalMockProcessorContext( + context = new InternalMockProcessorContext<>( dir, Serdes.String(), Serdes.String(), diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBTimeOrderedWindowStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBTimeOrderedWindowStoreTest.java index 2646c4cf472f4..95c88ed1576ce 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBTimeOrderedWindowStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBTimeOrderedWindowStoreTest.java @@ -59,7 +59,7 @@ public void setup() { windowStore = buildWindowStore(RETENTION_PERIOD, WINDOW_SIZE, true, Serdes.Integer(), Serdes.String()); recordCollector = new MockRecordCollector(); - context = new InternalMockProcessorContext( + context = new InternalMockProcessorContext<>( baseDir, Serdes.String(), Serdes.Integer(), diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBWindowStoreTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBWindowStoreTest.java index 5643cde53a98b..2b890f172d500 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBWindowStoreTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/RocksDBWindowStoreTest.java @@ -30,7 +30,6 @@ import org.apache.kafka.common.utils.Utils; import org.apache.kafka.streams.KeyValue; import org.apache.kafka.streams.processor.StateStoreContext; -import org.apache.kafka.streams.processor.internals.testutil.LogCaptureAppender; import org.apache.kafka.streams.state.Stores; import org.apache.kafka.streams.state.WindowStore; import org.apache.kafka.streams.state.WindowStoreIterator; @@ -70,16 +69,6 @@ WindowStore buildWindowStore(final long retentionPeriod, .build(); } - @Override - String getMetricsScope() { - return new RocksDbWindowBytesStoreSupplier(null, 0, 0, 0, false, false).metricsScope(); - } - - @Override - void setClassLoggerToDebug() { - LogCaptureAppender.setClassLoggerToDebug(AbstractRocksDBSegmentedBytesStore.class); - } - @Test public void shouldOnlyIterateOpenSegments() { long currentTime = 0; @@ -491,6 +480,7 @@ public void testInitialLoading() { ); } + @SuppressWarnings("unchecked") @Test public void testRestore() throws Exception { final long startTime = SEGMENT_INTERVAL * 2; diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/SegmentIteratorTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/SegmentIteratorTest.java index 97593e71c34f3..c7e59247e1376 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/SegmentIteratorTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/SegmentIteratorTest.java @@ -53,9 +53,10 @@ public class SegmentIteratorTest { private SegmentIterator iterator = null; + @SuppressWarnings("rawtypes") @Before public void before() { - final InternalMockProcessorContext context = new InternalMockProcessorContext( + final InternalMockProcessorContext context = new InternalMockProcessorContext<>( TestUtils.tempDirectory(), Serdes.String(), Serdes.String(), diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/TimestampedSegmentsTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/TimestampedSegmentsTest.java index 558f1c9471b1e..722cb69fd13f3 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/TimestampedSegmentsTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/TimestampedSegmentsTest.java @@ -55,7 +55,7 @@ public class TimestampedSegmentsTest { @Before public void createContext() { stateDirectory = TestUtils.tempDirectory(); - context = new InternalMockProcessorContext( + context = new InternalMockProcessorContext<>( stateDirectory, Serdes.String(), Serdes.Long(), diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/metrics/NamedCacheMetricsTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/metrics/NamedCacheMetricsTest.java index 2bb8482ba8d4d..9464a3a04cc87 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/metrics/NamedCacheMetricsTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/metrics/NamedCacheMetricsTest.java @@ -38,7 +38,6 @@ import static org.powermock.api.easymock.PowerMock.replay; import static org.powermock.api.easymock.PowerMock.verify; - @RunWith(PowerMockRunner.class) @PrepareForTest({StreamsMetricsImpl.class, Sensor.class}) public class NamedCacheMetricsTest { @@ -58,34 +57,18 @@ public class NamedCacheMetricsTest { public void shouldGetHitRatioSensorWithBuiltInMetricsVersionCurrent() { final String hitRatio = "hit-ratio"; mockStatic(StreamsMetricsImpl.class); - setUpStreamsMetrics(Version.LATEST, hitRatio); - replay(streamsMetrics); - replay(StreamsMetricsImpl.class); - - final Sensor sensor = NamedCacheMetrics.hitRatioSensor(streamsMetrics, THREAD_ID, TASK_ID, STORE_NAME); - - verifyResult(sensor); - } - - @Test - public void shouldGetHitRatioSensorWithBuiltInMetricsVersionBefore24() { - final Map parentTagMap = mkMap(mkEntry("key", "all")); - final String hitRatio = "hitRatio"; - final RecordingLevel recordingLevel = RecordingLevel.DEBUG; - mockStatic(StreamsMetricsImpl.class); - final Sensor parentSensor = mock(Sensor.class); - expect(streamsMetrics.taskLevelSensor(THREAD_ID, TASK_ID, hitRatio, recordingLevel)).andReturn(parentSensor); - expect(streamsMetrics.cacheLevelTagMap(THREAD_ID, TASK_ID, StreamsMetricsImpl.ROLLUP_VALUE)) - .andReturn(parentTagMap); + expect(streamsMetrics.version()).andStubReturn(Version.LATEST); + expect(streamsMetrics.cacheLevelSensor(THREAD_ID, TASK_ID, STORE_NAME, hitRatio, RecordingLevel.DEBUG)) + .andReturn(expectedSensor); + expect(streamsMetrics.cacheLevelTagMap(THREAD_ID, TASK_ID, STORE_NAME)).andReturn(tagMap); StreamsMetricsImpl.addAvgAndMinAndMaxToSensor( - parentSensor, + expectedSensor, StreamsMetricsImpl.CACHE_LEVEL_GROUP, - parentTagMap, + tagMap, hitRatio, HIT_RATIO_AVG_DESCRIPTION, HIT_RATIO_MIN_DESCRIPTION, HIT_RATIO_MAX_DESCRIPTION); - setUpStreamsMetrics(Version.FROM_0100_TO_24, hitRatio, parentSensor); replay(streamsMetrics); replay(StreamsMetricsImpl.class); @@ -94,23 +77,6 @@ public void shouldGetHitRatioSensorWithBuiltInMetricsVersionBefore24() { verifyResult(sensor); } - private void setUpStreamsMetrics(final Version builtInMetricsVersion, - final String hitRatio, - final Sensor... parents) { - expect(streamsMetrics.version()).andReturn(builtInMetricsVersion); - expect(streamsMetrics.cacheLevelSensor(THREAD_ID, TASK_ID, STORE_NAME, hitRatio, RecordingLevel.DEBUG, parents)) - .andReturn(expectedSensor); - expect(streamsMetrics.cacheLevelTagMap(THREAD_ID, TASK_ID, STORE_NAME)).andReturn(tagMap); - StreamsMetricsImpl.addAvgAndMinAndMaxToSensor( - expectedSensor, - StreamsMetricsImpl.CACHE_LEVEL_GROUP, - tagMap, - hitRatio, - HIT_RATIO_AVG_DESCRIPTION, - HIT_RATIO_MIN_DESCRIPTION, - HIT_RATIO_MAX_DESCRIPTION); - } - private void verifyResult(final Sensor sensor) { verify(streamsMetrics); verify(StreamsMetricsImpl.class); diff --git a/streams/src/test/java/org/apache/kafka/streams/state/internals/metrics/StateStoreMetricsTest.java b/streams/src/test/java/org/apache/kafka/streams/state/internals/metrics/StateStoreMetricsTest.java index 4e4d6c87061a8..fcbc06abd0b41 100644 --- a/streams/src/test/java/org/apache/kafka/streams/state/internals/metrics/StateStoreMetricsTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/state/internals/metrics/StateStoreMetricsTest.java @@ -23,15 +23,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.modules.junit4.PowerMockRunnerDelegate; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.function.Supplier; @@ -45,43 +39,22 @@ import static org.powermock.api.easymock.PowerMock.verify; @RunWith(PowerMockRunner.class) -@PowerMockRunnerDelegate(Parameterized.class) @PrepareForTest({StreamsMetricsImpl.class, Sensor.class}) public class StateStoreMetricsTest { - private static final String THREAD_ID = "test-thread"; private static final String TASK_ID = "test-task"; private static final String STORE_NAME = "test-store"; private static final String STORE_TYPE = "test-type"; - private static final String STORE_LEVEL_GROUP_FROM_0100_TO_24 = "stream-" + STORE_TYPE + "-state-metrics"; private static final String STORE_LEVEL_GROUP = "stream-state-metrics"; private static final String BUFFER_NAME = "test-buffer"; - private static final String BUFFER_LEVEL_GROUP_FROM_0100_TO_24 = "stream-buffer-metrics"; private final Sensor expectedSensor = createMock(Sensor.class); - private final Sensor parentSensor = createMock(Sensor.class); private final StreamsMetricsImpl streamsMetrics = createMock(StreamsMetricsImpl.class); private final Map storeTagMap = Collections.singletonMap("hello", "world"); - private final Map bufferTagMap = Collections.singletonMap("hi", "galaxy"); - private final Map allTagMap = Collections.singletonMap("hello", "universe"); - private String storeLevelGroup; - - @Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][] { - {Version.LATEST}, - {Version.FROM_0100_TO_24} - }); - } - - @Parameter - public Version builtInMetricsVersion; @Before public void setUp() { - storeLevelGroup = - builtInMetricsVersion == Version.FROM_0100_TO_24 ? STORE_LEVEL_GROUP_FROM_0100_TO_24 : STORE_LEVEL_GROUP; - expect(streamsMetrics.version()).andReturn(builtInMetricsVersion).anyTimes(); + expect(streamsMetrics.version()).andStubReturn(Version.LATEST); mockStatic(StreamsMetricsImpl.class); } @@ -89,16 +62,14 @@ public void setUp() { public void shouldGetPutSensor() { final String metricName = "put"; final String descriptionOfRate = "The average number of calls to put per second"; - final String descriptionOfCount = "The total number of calls to put"; final String descriptionOfAvg = "The average latency of calls to put"; final String descriptionOfMax = "The maximum latency of calls to put"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.putSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.putSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @@ -106,16 +77,14 @@ public void shouldGetPutSensor() { public void shouldGetPutIfAbsentSensor() { final String metricName = "put-if-absent"; final String descriptionOfRate = "The average number of calls to put-if-absent per second"; - final String descriptionOfCount = "The total number of calls to put-if-absent"; final String descriptionOfAvg = "The average latency of calls to put-if-absent"; final String descriptionOfMax = "The maximum latency of calls to put-if-absent"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.putIfAbsentSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.putIfAbsentSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @@ -123,16 +92,14 @@ public void shouldGetPutIfAbsentSensor() { public void shouldGetPutAllSensor() { final String metricName = "put-all"; final String descriptionOfRate = "The average number of calls to put-all per second"; - final String descriptionOfCount = "The total number of calls to put-all"; final String descriptionOfAvg = "The average latency of calls to put-all"; final String descriptionOfMax = "The maximum latency of calls to put-all"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.putAllSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.putAllSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @@ -140,16 +107,14 @@ public void shouldGetPutAllSensor() { public void shouldGetFetchSensor() { final String metricName = "fetch"; final String descriptionOfRate = "The average number of calls to fetch per second"; - final String descriptionOfCount = "The total number of calls to fetch"; final String descriptionOfAvg = "The average latency of calls to fetch"; final String descriptionOfMax = "The maximum latency of calls to fetch"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.fetchSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.fetchSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @@ -157,16 +122,14 @@ public void shouldGetFetchSensor() { public void shouldGetGetSensor() { final String metricName = "get"; final String descriptionOfRate = "The average number of calls to get per second"; - final String descriptionOfCount = "The total number of calls to get"; final String descriptionOfAvg = "The average latency of calls to get"; final String descriptionOfMax = "The maximum latency of calls to get"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.getSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.getSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @@ -174,16 +137,14 @@ public void shouldGetGetSensor() { public void shouldGetAllSensor() { final String metricName = "all"; final String descriptionOfRate = "The average number of calls to all per second"; - final String descriptionOfCount = "The total number of calls to all"; final String descriptionOfAvg = "The average latency of calls to all"; final String descriptionOfMax = "The maximum latency of calls to all"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.allSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.allSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @@ -191,16 +152,14 @@ public void shouldGetAllSensor() { public void shouldGetRangeSensor() { final String metricName = "range"; final String descriptionOfRate = "The average number of calls to range per second"; - final String descriptionOfCount = "The total number of calls to range"; final String descriptionOfAvg = "The average latency of calls to range"; final String descriptionOfMax = "The maximum latency of calls to range"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.rangeSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.rangeSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @@ -240,16 +199,14 @@ public void shouldGetPrefixScanSensor() { public void shouldGetFlushSensor() { final String metricName = "flush"; final String descriptionOfRate = "The average number of calls to flush per second"; - final String descriptionOfCount = "The total number of calls to flush"; final String descriptionOfAvg = "The average latency of calls to flush"; final String descriptionOfMax = "The maximum latency of calls to flush"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.flushSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.flushSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @@ -257,16 +214,14 @@ public void shouldGetFlushSensor() { public void shouldGetRemoveSensor() { final String metricName = "remove"; final String descriptionOfRate = "The average number of calls to remove per second"; - final String descriptionOfCount = "The total number of calls to remove"; final String descriptionOfAvg = "The average latency of calls to remove"; final String descriptionOfMax = "The maximum latency of calls to remove"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.removeSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.removeSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @@ -274,16 +229,14 @@ public void shouldGetRemoveSensor() { public void shouldGetDeleteSensor() { final String metricName = "delete"; final String descriptionOfRate = "The average number of calls to delete per second"; - final String descriptionOfCount = "The total number of calls to delete"; final String descriptionOfAvg = "The average latency of calls to delete"; final String descriptionOfMax = "The maximum latency of calls to delete"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.deleteSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.deleteSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @@ -291,46 +244,40 @@ public void shouldGetDeleteSensor() { public void shouldGetRestoreSensor() { final String metricName = "restore"; final String descriptionOfRate = "The average number of restorations per second"; - final String descriptionOfCount = "The total number of restorations"; final String descriptionOfAvg = "The average latency of restorations"; final String descriptionOfMax = "The maximum latency of restorations"; shouldGetSensor( metricName, descriptionOfRate, - descriptionOfCount, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.restoreSensor(THREAD_ID, TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) + () -> StateStoreMetrics.restoreSensor(TASK_ID, STORE_TYPE, STORE_NAME, streamsMetrics) ); } @Test public void shouldGetSuppressionBufferCountSensor() { final String metricName = "suppression-buffer-count"; - final String descriptionOfCurrentValue = "The current count of buffered records"; final String descriptionOfAvg = "The average count of buffered records"; final String descriptionOfMax = "The maximum count of buffered records"; shouldGetSuppressionBufferSensor( metricName, - descriptionOfCurrentValue, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.suppressionBufferCountSensor(THREAD_ID, TASK_ID, STORE_TYPE, BUFFER_NAME, streamsMetrics) + () -> StateStoreMetrics.suppressionBufferCountSensor(TASK_ID, STORE_TYPE, BUFFER_NAME, streamsMetrics) ); } @Test public void shouldGetSuppressionBufferSizeSensor() { final String metricName = "suppression-buffer-size"; - final String descriptionOfCurrentValue = "The current size of buffered records"; final String descriptionOfAvg = "The average size of buffered records"; final String descriptionOfMax = "The maximum size of buffered records"; shouldGetSuppressionBufferSensor( metricName, - descriptionOfCurrentValue, descriptionOfAvg, descriptionOfMax, - () -> StateStoreMetrics.suppressionBufferSizeSensor(THREAD_ID, TASK_ID, STORE_TYPE, BUFFER_NAME, streamsMetrics) + () -> StateStoreMetrics.suppressionBufferSizeSensor(TASK_ID, STORE_TYPE, BUFFER_NAME, streamsMetrics) ); } @@ -393,46 +340,26 @@ public void shouldGetRecordE2ELatencySensor() { private void shouldGetSensor(final String metricName, final String descriptionOfRate, - final String descriptionOfCount, final String descriptionOfAvg, final String descriptionOfMax, final Supplier sensorSupplier) { - if (builtInMetricsVersion == Version.FROM_0100_TO_24) { - setUpParentSensor(metricName, descriptionOfRate, descriptionOfCount, descriptionOfAvg, descriptionOfMax); - expect(streamsMetrics.storeLevelSensor( - TASK_ID, - STORE_NAME, - metricName, - RecordingLevel.DEBUG, - parentSensor - )).andReturn(expectedSensor); - StreamsMetricsImpl.addInvocationRateAndCountToSensor( - expectedSensor, - storeLevelGroup, - storeTagMap, - metricName, - descriptionOfRate, - descriptionOfCount - ); - } else { - expect(streamsMetrics.storeLevelSensor( - TASK_ID, - STORE_NAME, - metricName, - RecordingLevel.DEBUG - )).andReturn(expectedSensor); - StreamsMetricsImpl.addInvocationRateToSensor( - expectedSensor, - storeLevelGroup, - storeTagMap, - metricName, - descriptionOfRate - ); - } + expect(streamsMetrics.storeLevelSensor( + TASK_ID, + STORE_NAME, + metricName, + RecordingLevel.DEBUG + )).andReturn(expectedSensor); + StreamsMetricsImpl.addInvocationRateToSensor( + expectedSensor, + STORE_LEVEL_GROUP, + storeTagMap, + metricName, + descriptionOfRate + ); expect(streamsMetrics.storeLevelTagMap(TASK_ID, STORE_TYPE, STORE_NAME)).andReturn(storeTagMap); StreamsMetricsImpl.addAvgAndMaxToSensor( expectedSensor, - storeLevelGroup, + STORE_LEVEL_GROUP, storeTagMap, latencyMetricName(metricName), descriptionOfAvg, @@ -445,65 +372,22 @@ private void shouldGetSensor(final String metricName, verify(StreamsMetricsImpl.class, streamsMetrics); assertThat(sensor, is(expectedSensor)); } - private void setUpParentSensor(final String metricName, - final String descriptionOfRate, - final String descriptionOfCount, - final String descriptionOfAvg, - final String descriptionOfMax) { - expect(streamsMetrics.taskLevelSensor(THREAD_ID, TASK_ID, metricName, RecordingLevel.DEBUG)) - .andReturn(parentSensor); - expect(streamsMetrics.storeLevelTagMap(TASK_ID, STORE_TYPE, StreamsMetricsImpl.ROLLUP_VALUE)) - .andReturn(allTagMap); - StreamsMetricsImpl.addInvocationRateAndCountToSensor( - parentSensor, - storeLevelGroup, - allTagMap, - metricName, - descriptionOfRate, - descriptionOfCount - ); - StreamsMetricsImpl.addAvgAndMaxToSensor( - parentSensor, - storeLevelGroup, - allTagMap, - latencyMetricName(metricName), - descriptionOfAvg, - descriptionOfMax - ); - } private String latencyMetricName(final String metricName) { return metricName + StreamsMetricsImpl.LATENCY_SUFFIX; } private void shouldGetSuppressionBufferSensor(final String metricName, - final String descriptionOfCurrentValue, final String descriptionOfAvg, final String descriptionOfMax, final Supplier sensorSupplier) { - final String currentMetricName = metricName + "-current"; - final String group; final Map tagMap; expect(streamsMetrics.storeLevelSensor(TASK_ID, BUFFER_NAME, metricName, RecordingLevel.DEBUG)).andReturn(expectedSensor); - if (builtInMetricsVersion == Version.FROM_0100_TO_24) { - group = BUFFER_LEVEL_GROUP_FROM_0100_TO_24; - tagMap = bufferTagMap; - expect(streamsMetrics.bufferLevelTagMap(THREAD_ID, TASK_ID, BUFFER_NAME)).andReturn(tagMap); - StreamsMetricsImpl.addValueMetricToSensor( - expectedSensor, - group, - bufferTagMap, - currentMetricName, - descriptionOfCurrentValue - ); - } else { - group = STORE_LEVEL_GROUP; - tagMap = storeTagMap; - expect(streamsMetrics.storeLevelTagMap(TASK_ID, STORE_TYPE, BUFFER_NAME)).andReturn(tagMap); - } + tagMap = storeTagMap; + expect(streamsMetrics.storeLevelTagMap(TASK_ID, STORE_TYPE, BUFFER_NAME)).andReturn(tagMap); StreamsMetricsImpl.addAvgAndMaxToSensor( expectedSensor, - group, + STORE_LEVEL_GROUP, tagMap, metricName, descriptionOfAvg, diff --git a/streams/src/test/java/org/apache/kafka/test/InternalMockProcessorContext.java b/streams/src/test/java/org/apache/kafka/test/InternalMockProcessorContext.java index 8d39bf3b5ea4c..c3cf64af7e966 100644 --- a/streams/src/test/java/org/apache/kafka/test/InternalMockProcessorContext.java +++ b/streams/src/test/java/org/apache/kafka/test/InternalMockProcessorContext.java @@ -59,8 +59,8 @@ import static org.apache.kafka.streams.processor.internals.StateRestoreCallbackAdapter.adapt; -public class InternalMockProcessorContext - extends AbstractProcessorContext +public class InternalMockProcessorContext + extends AbstractProcessorContext implements RecordCollector.Supplier { private StateManager stateManager = new StateManagerStub(); @@ -202,8 +202,20 @@ public InternalMockProcessorContext(final File stateDir, final RecordCollector.Supplier collectorSupplier, final ThreadCache cache, final Time time) { + this(stateDir, keySerde, valueSerde, metrics, config, collectorSupplier, cache, time, new TaskId(0, 0)); + } + + public InternalMockProcessorContext(final File stateDir, + final Serde keySerde, + final Serde valueSerde, + final StreamsMetricsImpl metrics, + final StreamsConfig config, + final RecordCollector.Supplier collectorSupplier, + final ThreadCache cache, + final Time time, + final TaskId taskId) { super( - new TaskId(0, 0), + taskId, config, metrics, cache @@ -290,13 +302,13 @@ public Cancellable schedule(final Duration interval, public void commit() {} @Override - public void forward(final Record record) { + public void forward(final Record record) { forward(record, null); } @SuppressWarnings("unchecked") @Override - public void forward(final Record record, final String childName) { + public void forward(final Record record, final String childName) { if (recordContext != null && record.timestamp() != recordContext.timestamp()) { setTime(record.timestamp()); } diff --git a/streams/src/test/java/org/apache/kafka/test/MockInternalProcessorContext.java b/streams/src/test/java/org/apache/kafka/test/MockInternalProcessorContext.java index 370dca75ce774..c32c136d4f451 100644 --- a/streams/src/test/java/org/apache/kafka/test/MockInternalProcessorContext.java +++ b/streams/src/test/java/org/apache/kafka/test/MockInternalProcessorContext.java @@ -40,7 +40,7 @@ import java.util.Properties; import org.apache.kafka.streams.state.internals.ThreadCache.DirtyEntryFlushListener; -public class MockInternalProcessorContext extends MockProcessorContext implements InternalProcessorContext { +public class MockInternalProcessorContext extends MockProcessorContext implements InternalProcessorContext { private final Map restoreCallbacks = new LinkedHashMap<>(); private ProcessorNode currentNode; diff --git a/streams/src/test/java/org/apache/kafka/test/MockProcessorNode.java b/streams/src/test/java/org/apache/kafka/test/MockProcessorNode.java index a75c25067a888..4ab4cb8bbbcd3 100644 --- a/streams/src/test/java/org/apache/kafka/test/MockProcessorNode.java +++ b/streams/src/test/java/org/apache/kafka/test/MockProcessorNode.java @@ -53,7 +53,7 @@ private MockProcessorNode(final MockProcessor mockProcessor) { } @Override - public void init(final InternalProcessorContext context) { + public void init(final InternalProcessorContext context) { super.init(context); initialized = true; } diff --git a/streams/src/test/java/org/apache/kafka/test/MockSourceNode.java b/streams/src/test/java/org/apache/kafka/test/MockSourceNode.java index 9d22e3b78a3b7..f52134e5b493e 100644 --- a/streams/src/test/java/org/apache/kafka/test/MockSourceNode.java +++ b/streams/src/test/java/org/apache/kafka/test/MockSourceNode.java @@ -24,7 +24,7 @@ import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; -public class MockSourceNode extends SourceNode { +public class MockSourceNode extends SourceNode { private static final String NAME = "MOCK-SOURCE-"; private static final AtomicInteger INDEX = new AtomicInteger(1); @@ -47,7 +47,7 @@ public void process(final Record record) { } @Override - public void init(final InternalProcessorContext context) { + public void init(final InternalProcessorContext context) { super.init(context); initialized = true; } diff --git a/streams/src/test/java/org/apache/kafka/test/NoOpProcessorContext.java b/streams/src/test/java/org/apache/kafka/test/NoOpProcessorContext.java index a3ec02b972f8f..c42601191b432 100644 --- a/streams/src/test/java/org/apache/kafka/test/NoOpProcessorContext.java +++ b/streams/src/test/java/org/apache/kafka/test/NoOpProcessorContext.java @@ -44,7 +44,7 @@ import org.apache.kafka.streams.state.internals.ThreadCache; import org.apache.kafka.streams.state.internals.ThreadCache.DirtyEntryFlushListener; -public class NoOpProcessorContext extends AbstractProcessorContext { +public class NoOpProcessorContext extends AbstractProcessorContext { public boolean initialized; @SuppressWarnings("WeakerAccess") public Map forwardedValues = new HashMap<>(); @@ -147,6 +147,6 @@ public void registerCacheFlushListener(final String namespace, final DirtyEntryF @Override public String changelogFor(final String storeName) { - return ProcessorStateManager.storeChangelogTopic(applicationId(), storeName); + return ProcessorStateManager.storeChangelogTopic(applicationId(), storeName, taskId().namedTopology()); } } diff --git a/streams/streams-scala/src/main/scala/org/apache/kafka/streams/scala/FunctionConversions.scala b/streams/streams-scala/src/main/scala/org/apache/kafka/streams/scala/FunctionConversions.scala deleted file mode 100644 index 892419a6cd8bf..0000000000000 --- a/streams/streams-scala/src/main/scala/org/apache/kafka/streams/scala/FunctionConversions.scala +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.streams.scala - -import org.apache.kafka.streams.KeyValue -import org.apache.kafka.streams.kstream._ -import scala.jdk.CollectionConverters._ -import java.lang.{Iterable => JIterable} - -@deprecated("This object is for internal use only", since = "2.1.0") -object FunctionConversions { - - implicit private[scala] class ForeachActionFromFunction[K, V](val p: (K, V) => Unit) extends AnyVal { - def asForeachAction: ForeachAction[K, V] = (key, value) => p(key, value) - } - - implicit class PredicateFromFunction[K, V](val p: (K, V) => Boolean) extends AnyVal { - def asPredicate: Predicate[K, V] = (key: K, value: V) => p(key, value) - } - - implicit class MapperFromFunction[T, U, VR](val f: (T, U) => VR) extends AnyVal { - def asKeyValueMapper: KeyValueMapper[T, U, VR] = (key: T, value: U) => f(key, value) - def asValueJoiner: ValueJoiner[T, U, VR] = (value1: T, value2: U) => f(value1, value2) - } - - implicit class KeyValueMapperFromFunction[K, V, KR, VR](val f: (K, V) => (KR, VR)) extends AnyVal { - def asKeyValueMapper: KeyValueMapper[K, V, KeyValue[KR, VR]] = (key: K, value: V) => { - val (kr, vr) = f(key, value) - KeyValue.pair(kr, vr) - } - } - - implicit class ValueMapperFromFunction[V, VR](val f: V => VR) extends AnyVal { - def asValueMapper: ValueMapper[V, VR] = (value: V) => f(value) - } - - implicit class FlatValueMapperFromFunction[V, VR](val f: V => Iterable[VR]) extends AnyVal { - def asValueMapper: ValueMapper[V, JIterable[VR]] = (value: V) => f(value).asJava - } - - implicit class ValueMapperWithKeyFromFunction[K, V, VR](val f: (K, V) => VR) extends AnyVal { - def asValueMapperWithKey: ValueMapperWithKey[K, V, VR] = (readOnlyKey: K, value: V) => f(readOnlyKey, value) - } - - implicit class FlatValueMapperWithKeyFromFunction[K, V, VR](val f: (K, V) => Iterable[VR]) extends AnyVal { - def asValueMapperWithKey: ValueMapperWithKey[K, V, JIterable[VR]] = - (readOnlyKey: K, value: V) => f(readOnlyKey, value).asJava - } - - implicit class AggregatorFromFunction[K, V, VA](val f: (K, V, VA) => VA) extends AnyVal { - def asAggregator: Aggregator[K, V, VA] = (key: K, value: V, aggregate: VA) => f(key, value, aggregate) - } - - implicit class MergerFromFunction[K, VR](val f: (K, VR, VR) => VR) extends AnyVal { - def asMerger: Merger[K, VR] = (aggKey: K, aggOne: VR, aggTwo: VR) => f(aggKey, aggOne, aggTwo) - } - - implicit class ReducerFromFunction[V](val f: (V, V) => V) extends AnyVal { - def asReducer: Reducer[V] = (value1: V, value2: V) => f(value1, value2) - } - - implicit class InitializerFromFunction[VA](val f: () => VA) extends AnyVal { - def asInitializer: Initializer[VA] = () => f() - } - - implicit class TransformerSupplierFromFunction[K, V, VO](val f: () => Transformer[K, V, VO]) extends AnyVal { - def asTransformerSupplier: TransformerSupplier[K, V, VO] = () => f() - } -} diff --git a/streams/streams-scala/src/main/scala/org/apache/kafka/streams/scala/kstream/KTable.scala b/streams/streams-scala/src/main/scala/org/apache/kafka/streams/scala/kstream/KTable.scala index ccabeb7ee2bbe..7aba69d50e722 100644 --- a/streams/streams-scala/src/main/scala/org/apache/kafka/streams/scala/kstream/KTable.scala +++ b/streams/streams-scala/src/main/scala/org/apache/kafka/streams/scala/kstream/KTable.scala @@ -304,7 +304,7 @@ class KTable[K, V](val inner: KTableJ[K, V]) { new KStream(inner.toStream[KR](mapper.asKeyValueMapper, named)) /** - * Suppress some updates from this changelog stream, determined by the supplied [[Suppressed]] configuration. + * Suppress some updates from this changelog stream, determined by the supplied [[org.apache.kafka.streams.kstream.Suppressed]] configuration. * * This controls what updates downstream table and stream operations will receive. * diff --git a/streams/streams-scala/src/main/scala/org/apache/kafka/streams/scala/kstream/Suppressed.scala b/streams/streams-scala/src/main/scala/org/apache/kafka/streams/scala/kstream/Suppressed.scala deleted file mode 100644 index 22b20b98cd19d..0000000000000 --- a/streams/streams-scala/src/main/scala/org/apache/kafka/streams/scala/kstream/Suppressed.scala +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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.streams.scala.kstream - -import java.time.Duration - -import org.apache.kafka.streams.kstream.{Windowed, Suppressed => SupressedJ} -import org.apache.kafka.streams.kstream.Suppressed.{ - EagerBufferConfig, - StrictBufferConfig, - BufferConfig => BufferConfigJ -} -import org.apache.kafka.streams.kstream.internals.suppress.{ - EagerBufferConfigImpl, - FinalResultsSuppressionBuilder, - StrictBufferConfigImpl, - SuppressedInternal -} - -/** - * Duplicates the static factory methods inside the Java interface [[org.apache.kafka.streams.kstream.Suppressed]]. - * - * This was required for compatibility w/ Scala 2.11 + Java 1.8 because the Scala 2.11 compiler doesn't support the use - * of static methods inside Java interfaces. We have since dropped Scala 2.11 support. - */ -@deprecated(message = "Use org.apache.kafka.streams.kstream.Suppressed", since = "2.5") -object Suppressed { - - /** - * Configure the suppression to emit only the "final results" from the window. - * - * By default all Streams operators emit results whenever new results are available. - * This includes windowed operations. - * - * This configuration will instead emit just one result per key for each window, guaranteeing - * to deliver only the final result. This option is suitable for use cases in which the business logic - * requires a hard guarantee that only the final result is propagated. For example, sending alerts. - * - * To accomplish this, the operator will buffer events from the window until the window close (that is, - * until the end-time passes, and additionally until the grace period expires). Since windowed operators - * are required to reject late events for a window whose grace period is expired, there is an additional - * guarantee that the final results emitted from this suppression will match any queriable state upstream. - * - * @param bufferConfig A configuration specifying how much space to use for buffering intermediate results. - * This is required to be a "strict" config, since it would violate the "final results" - * property to emit early and then issue an update later. - * @tparam K The [[Windowed]] key type for the KTable to apply this suppression to. - * @return a "final results" mode suppression configuration - * @see [[org.apache.kafka.streams.kstream.Suppressed.untilTimeLimit]] - */ - def untilWindowCloses[K](bufferConfig: StrictBufferConfig): SupressedJ[Windowed[K]] = - new FinalResultsSuppressionBuilder[Windowed[K]](null, bufferConfig) - - /** - * Configure the suppression to wait `timeToWaitForMoreEvents` amount of time after receiving a record - * before emitting it further downstream. If another record for the same key arrives in the mean time, it replaces - * the first record in the buffer but does not re-start the timer. - * - * @param timeToWaitForMoreEvents The amount of time to wait, per record, for new events. - * @param bufferConfig A configuration specifying how much space to use for buffering intermediate results. - * @tparam K The key type for the KTable to apply this suppression to. - * @return a suppression configuration - * @see [[org.apache.kafka.streams.kstream.Suppressed.untilTimeLimit]] - */ - def untilTimeLimit[K](timeToWaitForMoreEvents: Duration, bufferConfig: BufferConfigJ[_]): SupressedJ[K] = - new SuppressedInternal[K](null, timeToWaitForMoreEvents, bufferConfig, null, false) - - /** - * Duplicates the static factory methods inside the Java interface - * [[org.apache.kafka.streams.kstream.Suppressed.BufferConfig]]. - */ - object BufferConfig { - - /** - * Create a size-constrained buffer in terms of the maximum number of keys it will store. - * - * @param recordLimit maximum number of keys to buffer. - * @return size-constrained buffer in terms of the maximum number of keys it will store. - * @see [[org.apache.kafka.streams.kstream.Suppressed.BufferConfig.maxRecords]] - */ - def maxRecords(recordLimit: Long): EagerBufferConfig = - new EagerBufferConfigImpl(recordLimit, Long.MaxValue) - - /** - * Create a size-constrained buffer in terms of the maximum number of bytes it will use. - * - * @param byteLimit maximum number of bytes to buffer. - * @return size-constrained buffer in terms of the maximum number of bytes it will use. - * @see [[org.apache.kafka.streams.kstream.Suppressed.BufferConfig.maxBytes]] - */ - def maxBytes(byteLimit: Long): EagerBufferConfig = - new EagerBufferConfigImpl(Long.MaxValue, byteLimit) - - /** - * Create a buffer unconstrained by size (either keys or bytes). - * - * As a result, the buffer will consume as much memory as it needs, dictated by the time bound. - * - * If there isn't enough heap available to meet the demand, the application will encounter an - * [[OutOfMemoryError]] and shut down (not guaranteed to be a graceful exit). Also, note that - * JVM processes under extreme memory pressure may exhibit poor GC behavior. - * - * This is a convenient option if you doubt that your buffer will be that large, but also don't - * wish to pick particular constraints, such as in testing. - * - * This buffer is "strict" in the sense that it will enforce the time bound or crash. - * It will never emit early. - * - * @return a buffer unconstrained by size (either keys or bytes). - * @see [[org.apache.kafka.streams.kstream.Suppressed.BufferConfig.unbounded]] - */ - def unbounded(): StrictBufferConfig = new StrictBufferConfigImpl() - } -} diff --git a/streams/streams-scala/src/test/scala/org/apache/kafka/streams/scala/kstream/SuppressedTest.scala b/streams/streams-scala/src/test/scala/org/apache/kafka/streams/scala/kstream/SuppressedTest.scala deleted file mode 100644 index 86936a21eca4a..0000000000000 --- a/streams/streams-scala/src/test/scala/org/apache/kafka/streams/scala/kstream/SuppressedTest.scala +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.streams.scala.kstream - -import org.apache.kafka.streams.kstream.internals.suppress._ -import org.apache.kafka.streams.scala.kstream.Suppressed.BufferConfig -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -import java.time.Duration - -@deprecated(message = "org.apache.kafka.streams.scala.kstream.Suppressed has been deprecated", since = "2.5") -class SuppressedTest { - - @Test - def testProduceCorrectSuppressionUntilWindowCloses(): Unit = { - val bufferConfig = BufferConfig.unbounded() - val suppression = Suppressed.untilWindowCloses[String](bufferConfig) - assertEquals(suppression, new FinalResultsSuppressionBuilder(null, bufferConfig)) - assertEquals(suppression.withName("soup"), new FinalResultsSuppressionBuilder("soup", bufferConfig)) - } - - @Test - def testProduceCorrectSuppressionUntilTimeLimit(): Unit = { - val bufferConfig = BufferConfig.unbounded() - val duration = Duration.ofMillis(1) - assertEquals(Suppressed.untilTimeLimit[String](duration, bufferConfig), - new SuppressedInternal[String](null, duration, bufferConfig, null, false)) - } - - @Test - def testProduceCorrectBufferConfigWithMaxRecords(): Unit = { - assertEquals(BufferConfig.maxRecords(4), new EagerBufferConfigImpl(4, Long.MaxValue)) - assertEquals(BufferConfig.maxRecords(4).withMaxBytes(5), new EagerBufferConfigImpl(4, 5)) - } - - @Test - def testProduceCorrectBufferConfigWithMaxBytes(): Unit = { - assertEquals(BufferConfig.maxBytes(4), new EagerBufferConfigImpl(Long.MaxValue, 4)) - assertEquals(BufferConfig.maxBytes(4).withMaxRecords(5), new EagerBufferConfigImpl(5, 4)) - } - - @Test - def testProduceCorrectBufferConfigWithUnbounded(): Unit = - assertEquals(BufferConfig.unbounded(), - new StrictBufferConfigImpl(Long.MaxValue, Long.MaxValue, BufferFullStrategy.SHUT_DOWN)) - - @Test - def testSupportLongChainsOfFactoryMethods(): Unit = { - val bc1 = BufferConfig - .unbounded() - .emitEarlyWhenFull() - .withMaxRecords(3L) - .withMaxBytes(4L) - .withMaxRecords(5L) - .withMaxBytes(6L) - assertEquals(new EagerBufferConfigImpl(5L, 6L), bc1) - assertEquals(new StrictBufferConfigImpl(5L, 6L, BufferFullStrategy.SHUT_DOWN), bc1.shutDownWhenFull()) - - val bc2 = BufferConfig - .maxBytes(4) - .withMaxRecords(5) - .withMaxBytes(6) - .withNoBound() - .withMaxBytes(7) - .withMaxRecords(8) - - assertEquals(new StrictBufferConfigImpl(8L, 7L, BufferFullStrategy.SHUT_DOWN), bc2) - assertEquals(BufferConfig.unbounded(), bc2.withNoBound()) - - val bc3 = BufferConfig - .maxRecords(5L) - .withMaxBytes(10L) - .emitEarlyWhenFull() - .withMaxRecords(11L) - - assertEquals(new EagerBufferConfigImpl(11L, 10L), bc3) - } -} diff --git a/streams/test-utils/src/main/java/org/apache/kafka/streams/TopologyTestDriver.java b/streams/test-utils/src/main/java/org/apache/kafka/streams/TopologyTestDriver.java index 779a84f56a11f..ad3643ff2bccc 100644 --- a/streams/test-utils/src/main/java/org/apache/kafka/streams/TopologyTestDriver.java +++ b/streams/test-utils/src/main/java/org/apache/kafka/streams/TopologyTestDriver.java @@ -68,6 +68,7 @@ import org.apache.kafka.streams.processor.internals.StreamThread; import org.apache.kafka.streams.processor.internals.StreamsProducer; import org.apache.kafka.streams.processor.internals.Task; +import org.apache.kafka.streams.processor.internals.TopologyMetadata; import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl; import org.apache.kafka.streams.processor.internals.metrics.TaskMetrics; import org.apache.kafka.streams.state.KeyValueIterator; @@ -398,7 +399,7 @@ private StreamsMetricsImpl setupMetrics(final StreamsConfig streamsConfig) { streamsConfig.getString(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG), mockWallClockTime ); - TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor(threadId, TASK_ID.toString(), streamsMetrics); + TaskMetrics.droppedRecordsSensor(threadId, TASK_ID.toString(), streamsMetrics); return streamsMetrics; } @@ -417,9 +418,8 @@ private void setupTopology(final InternalTopologyBuilder builder, offsetsByTopicOrPatternPartition.put(tp, new AtomicLong()); } - final boolean createStateDirectory = processorTopology.hasPersistentLocalStore() || - (globalTopology != null && globalTopology.hasPersistentGlobalStore()); - stateDirectory = new StateDirectory(streamsConfig, mockWallClockTime, createStateDirectory, builder.hasNamedTopologies()); + // TODO KAFKA-12648: The TTD does not yet work with NamedTopologies, so just pass in false + stateDirectory = new StateDirectory(streamsConfig, mockWallClockTime, internalTopologyBuilder.hasPersistentStores(), false); } private void setupGlobalTask(final Time mockWallClockTime, @@ -879,7 +879,7 @@ final boolean isEmpty(final String topic) { */ public Map getAllStateStores() { final Map allStores = new HashMap<>(); - for (final String storeName : internalTopologyBuilder.allStateStoreName()) { + for (final String storeName : internalTopologyBuilder.allStateStoreNames()) { allStores.put(storeName, getStateStore(storeName, false)); } return allStores; diff --git a/streams/test-utils/src/main/java/org/apache/kafka/streams/processor/MockProcessorContext.java b/streams/test-utils/src/main/java/org/apache/kafka/streams/processor/MockProcessorContext.java index 9cbe2590e94d8..b5ced0feaa8a5 100644 --- a/streams/test-utils/src/main/java/org/apache/kafka/streams/processor/MockProcessorContext.java +++ b/streams/test-utils/src/main/java/org/apache/kafka/streams/processor/MockProcessorContext.java @@ -237,7 +237,7 @@ public MockProcessorContext(final Properties config, final TaskId taskId, final streamsConfig.getString(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG), Time.SYSTEM ); - TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor(threadId, taskId.toString(), metrics); + TaskMetrics.droppedRecordsSensor(threadId, taskId.toString(), metrics); } @Override diff --git a/streams/test-utils/src/main/java/org/apache/kafka/streams/processor/api/MockProcessorContext.java b/streams/test-utils/src/main/java/org/apache/kafka/streams/processor/api/MockProcessorContext.java index 1032ad7a55071..ffc2940e3b4ca 100644 --- a/streams/test-utils/src/main/java/org/apache/kafka/streams/processor/api/MockProcessorContext.java +++ b/streams/test-utils/src/main/java/org/apache/kafka/streams/processor/api/MockProcessorContext.java @@ -257,7 +257,7 @@ public MockProcessorContext(final Properties config, final TaskId taskId, final streamsConfig.getString(StreamsConfig.BUILT_IN_METRICS_VERSION_CONFIG), Time.SYSTEM ); - TaskMetrics.droppedRecordsSensorOrSkippedRecordsSensor(threadId, taskId.toString(), metrics); + TaskMetrics.droppedRecordsSensor(threadId, taskId.toString(), metrics); } @Override diff --git a/tests/docker/Dockerfile b/tests/docker/Dockerfile index e8af730bffeeb..ab5484d4116b5 100644 --- a/tests/docker/Dockerfile +++ b/tests/docker/Dockerfile @@ -34,7 +34,7 @@ LABEL ducker.creator=$ducker_creator # Update Linux and install necessary utilities. # we have to install git since it is included in openjdk:8 but not openjdk:11 RUN apt update && apt install -y sudo git netcat iptables rsync unzip wget curl jq coreutils openssh-server net-tools vim python3-pip python3-dev libffi-dev libssl-dev cmake pkg-config libfuse-dev iperf traceroute && apt-get -y clean -RUN python3 -m pip install -U pip==20.2.2; +RUN python3 -m pip install -U pip==21.1.1; RUN pip3 install --upgrade cffi virtualenv pyasn1 boto3 pycrypto pywinrm ipaddress enum34 && pip3 install --upgrade ducktape==0.8.1 # Set up ssh diff --git a/trogdor/src/main/java/org/apache/kafka/trogdor/workload/ConsumeBenchWorker.java b/trogdor/src/main/java/org/apache/kafka/trogdor/workload/ConsumeBenchWorker.java index f6067b59af4e9..84ce1d333f18b 100644 --- a/trogdor/src/main/java/org/apache/kafka/trogdor/workload/ConsumeBenchWorker.java +++ b/trogdor/src/main/java/org/apache/kafka/trogdor/workload/ConsumeBenchWorker.java @@ -282,7 +282,6 @@ public Void call() throws Exception { log.info("{} Consumed total number of messages={}, bytes={} in {} ms. status: {}", clientId, messagesConsumed, bytesConsumed, curTimeMs - startTimeMs, statusData); } - doneFuture.complete(""); consumer.close(); return null; } @@ -307,6 +306,7 @@ public void run() { } statusUpdaterFuture.cancel(false); statusUpdater.update(); + doneFuture.complete(""); } }