diff --git a/checkstyle/suppressions.xml b/checkstyle/suppressions.xml
index 5473b035ea919..2ed1fac5df95d 100644
--- a/checkstyle/suppressions.xml
+++ b/checkstyle/suppressions.xml
@@ -268,6 +268,8 @@
files="(ReplicationControlManager|ReplicationControlManagerTest).java"/>
+
waitForReadyBrokers(int minBrokers) {
throw new UnsupportedOperationException();
}
+ @Override
+ public CompletableFuture beginWritingSnapshot() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void beginShutdown() {
this.active = false;
diff --git a/metadata/src/main/java/org/apache/kafka/controller/ClientQuotaControlManager.java b/metadata/src/main/java/org/apache/kafka/controller/ClientQuotaControlManager.java
index 9b8e2d683b650..f6a24973b58f2 100644
--- a/metadata/src/main/java/org/apache/kafka/controller/ClientQuotaControlManager.java
+++ b/metadata/src/main/java/org/apache/kafka/controller/ClientQuotaControlManager.java
@@ -21,6 +21,7 @@
import org.apache.kafka.common.config.internals.QuotaConfigs;
import org.apache.kafka.common.errors.InvalidRequestException;
import org.apache.kafka.common.metadata.QuotaRecord;
+import org.apache.kafka.common.metadata.QuotaRecord.EntityData;
import org.apache.kafka.common.protocol.Errors;
import org.apache.kafka.common.quota.ClientQuotaAlteration;
import org.apache.kafka.common.quota.ClientQuotaEntity;
@@ -35,18 +36,20 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class ClientQuotaControlManager {
-
private final SnapshotRegistry snapshotRegistry;
- final TimelineHashMap> clientQuotaData;
+ final TimelineHashMap> clientQuotaData;
ClientQuotaControlManager(SnapshotRegistry snapshotRegistry) {
this.snapshotRegistry = snapshotRegistry;
@@ -98,7 +101,7 @@ public void replay(QuotaRecord record) {
Map entityMap = new HashMap<>(2);
record.entity().forEach(entityData -> entityMap.put(entityData.entityType(), entityData.entityName()));
ClientQuotaEntity entity = new ClientQuotaEntity(entityMap);
- Map quotas = clientQuotaData.get(entity);
+ TimelineHashMap quotas = clientQuotaData.get(entity);
if (quotas == null) {
quotas = new TimelineHashMap<>(snapshotRegistry, 0);
clientQuotaData.put(entity, quotas);
@@ -136,14 +139,15 @@ private void alterClientQuotaEntity(
}
// Don't share objects between different records
- Supplier> recordEntitySupplier = () ->
- validatedEntityMap.entrySet().stream().map(mapEntry -> new QuotaRecord.EntityData()
+ Supplier> recordEntitySupplier = () ->
+ validatedEntityMap.entrySet().stream().map(mapEntry -> new EntityData()
.setEntityType(mapEntry.getKey())
.setEntityName(mapEntry.getValue()))
.collect(Collectors.toList());
List newRecords = new ArrayList<>(newQuotaConfigs.size());
- Map currentQuotas = clientQuotaData.getOrDefault(entity, Collections.emptyMap());
+ Map currentQuotas = clientQuotaData.containsKey(entity) ?
+ clientQuotaData.get(entity) : Collections.emptyMap();
newQuotaConfigs.forEach((key, newValue) -> {
if (newValue == null) {
if (currentQuotas.containsKey(key)) {
@@ -249,7 +253,7 @@ private ApiError validateEntity(ClientQuotaEntity entity, Map va
return new ApiError(Errors.INVALID_REQUEST, "Invalid empty client quota entity");
}
- for (Map.Entry entityEntry : entity.entries().entrySet()) {
+ for (Entry entityEntry : entity.entries().entrySet()) {
String entityType = entityEntry.getKey();
String entityName = entityEntry.getValue();
if (validatedEntityMap.containsKey(entityType)) {
@@ -272,4 +276,44 @@ private ApiError validateEntity(ClientQuotaEntity entity, Map va
return ApiError.NONE;
}
+
+ class ClientQuotaControlIterator implements Iterator> {
+ private final long epoch;
+ private final Iterator>> iterator;
+
+ ClientQuotaControlIterator(long epoch) {
+ this.epoch = epoch;
+ this.iterator = clientQuotaData.entrySet(epoch).iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public List next() {
+ if (!hasNext()) throw new NoSuchElementException();
+ Entry> entry = iterator.next();
+ ClientQuotaEntity entity = entry.getKey();
+ List records = new ArrayList<>();
+ for (Entry quotaEntry : entry.getValue().entrySet(epoch)) {
+ QuotaRecord record = new QuotaRecord();
+ for (Entry entityEntry : entity.entries().entrySet()) {
+ record.entity().add(new EntityData().
+ setEntityType(entityEntry.getKey()).
+ setEntityName(entityEntry.getValue()));
+ }
+ record.setKey(quotaEntry.getKey());
+ record.setValue(quotaEntry.getValue());
+ record.setRemove(false);
+ records.add(new ApiMessageAndVersion(record, (short) 0));
+ }
+ return records;
+ }
+ }
+
+ ClientQuotaControlIterator iterator(long epoch) {
+ return new ClientQuotaControlIterator(epoch);
+ }
}
diff --git a/metadata/src/main/java/org/apache/kafka/controller/ClusterControlManager.java b/metadata/src/main/java/org/apache/kafka/controller/ClusterControlManager.java
index 4748d195986ab..05380fc49e421 100644
--- a/metadata/src/main/java/org/apache/kafka/controller/ClusterControlManager.java
+++ b/metadata/src/main/java/org/apache/kafka/controller/ClusterControlManager.java
@@ -24,6 +24,10 @@
import org.apache.kafka.common.message.BrokerRegistrationRequestData;
import org.apache.kafka.common.metadata.FenceBrokerRecord;
import org.apache.kafka.common.metadata.RegisterBrokerRecord;
+import org.apache.kafka.common.metadata.RegisterBrokerRecord.BrokerEndpoint;
+import org.apache.kafka.common.metadata.RegisterBrokerRecord.BrokerEndpointCollection;
+import org.apache.kafka.common.metadata.RegisterBrokerRecord.BrokerFeature;
+import org.apache.kafka.common.metadata.RegisterBrokerRecord.BrokerFeatureCollection;
import org.apache.kafka.common.metadata.UnfenceBrokerRecord;
import org.apache.kafka.common.metadata.UnregisterBrokerRecord;
import org.apache.kafka.common.security.auth.SecurityProtocol;
@@ -40,8 +44,11 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -184,7 +191,7 @@ public ControllerResult registerBroker(
setBrokerEpoch(brokerEpoch).
setRack(request.rack());
for (BrokerRegistrationRequestData.Listener listener : request.listeners()) {
- record.endPoints().add(new RegisterBrokerRecord.BrokerEndpoint().
+ record.endPoints().add(new BrokerEndpoint().
setHost(listener.host()).
setName(listener.name()).
setPort(listener.port()).
@@ -199,7 +206,7 @@ public ControllerResult registerBroker(
"the broker has an unsupported version of " + feature.name());
}
}
- record.features().add(new RegisterBrokerRecord.BrokerFeature().
+ record.features().add(new BrokerFeature().
setName(feature.name()).
setMinSupportedVersion(feature.minSupportedVersion()).
setMaxSupportedVersion(feature.maxSupportedVersion()));
@@ -219,13 +226,13 @@ public ControllerResult registerBroker(
public void replay(RegisterBrokerRecord record) {
int brokerId = record.brokerId();
List listeners = new ArrayList<>();
- for (RegisterBrokerRecord.BrokerEndpoint endpoint : record.endPoints()) {
+ for (BrokerEndpoint endpoint : record.endPoints()) {
listeners.add(new Endpoint(endpoint.name(),
SecurityProtocol.forId(endpoint.securityProtocol()),
endpoint.host(), endpoint.port()));
}
Map features = new HashMap<>();
- for (RegisterBrokerRecord.BrokerFeature feature : record.features()) {
+ for (BrokerFeature feature : record.features()) {
features.put(feature.name(), new VersionRange(
feature.minSupportedVersion(), feature.maxSupportedVersion()));
}
@@ -343,4 +350,56 @@ public void addReadyBrokersFuture(CompletableFuture future, int minBrokers
readyBrokersFuture = Optional.empty();
}
}
+
+ class ClusterControlIterator implements Iterator> {
+ private final Iterator> iterator;
+
+ ClusterControlIterator(long epoch) {
+ this.iterator = brokerRegistrations.entrySet(epoch).iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public List next() {
+ if (!hasNext()) throw new NoSuchElementException();
+ Entry entry = iterator.next();
+ int brokerId = entry.getKey();
+ BrokerRegistration registration = entry.getValue();
+ BrokerEndpointCollection endpoints = new BrokerEndpointCollection();
+ for (Entry endpointEntry : registration.listeners().entrySet()) {
+ endpoints.add(new BrokerEndpoint().setName(endpointEntry.getKey()).
+ setHost(endpointEntry.getValue().host()).
+ setPort(endpointEntry.getValue().port()).
+ setSecurityProtocol(endpointEntry.getValue().securityProtocol().id));
+ }
+ BrokerFeatureCollection features = new BrokerFeatureCollection();
+ for (Entry featureEntry : registration.supportedFeatures().entrySet()) {
+ features.add(new BrokerFeature().setName(featureEntry.getKey()).
+ setMaxSupportedVersion(featureEntry.getValue().max()).
+ setMinSupportedVersion(featureEntry.getValue().min()));
+ }
+ List batch = new ArrayList<>();
+ batch.add(new ApiMessageAndVersion(new RegisterBrokerRecord().
+ setBrokerId(brokerId).
+ setIncarnationId(registration.incarnationId()).
+ setBrokerEpoch(registration.epoch()).
+ setEndPoints(endpoints).
+ setFeatures(features).
+ setRack(registration.rack().orElse(null)), (short) 0));
+ if (!registration.fenced()) {
+ batch.add(new ApiMessageAndVersion(new UnfenceBrokerRecord().
+ setId(brokerId).
+ setEpoch(registration.epoch()), (short) 0));
+ }
+ return batch;
+ }
+ }
+
+ ClusterControlIterator iterator(long epoch) {
+ return new ClusterControlIterator(epoch);
+ }
}
diff --git a/metadata/src/main/java/org/apache/kafka/controller/ConfigurationControlManager.java b/metadata/src/main/java/org/apache/kafka/controller/ConfigurationControlManager.java
index dcfe92d46a54b..3e9e9e1d593c8 100644
--- a/metadata/src/main/java/org/apache/kafka/controller/ConfigurationControlManager.java
+++ b/metadata/src/main/java/org/apache/kafka/controller/ConfigurationControlManager.java
@@ -40,10 +40,12 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.NoSuchElementException;
import java.util.Objects;
import static org.apache.kafka.clients.admin.AlterConfigOp.OpType.APPEND;
+
public class ConfigurationControlManager {
private final Logger log;
private final SnapshotRegistry snapshotRegistry;
@@ -316,6 +318,9 @@ public void replay(ConfigRecord record) {
} else {
configs.put(record.name(), record.value());
}
+ if (configs.isEmpty()) {
+ configData.remove(configResource);
+ }
log.info("{}: set configuration {} to {}", configResource, record.name(), record.value());
}
@@ -368,4 +373,39 @@ public Map>> describeConfigs(
void deleteTopicConfigs(String name) {
configData.remove(new ConfigResource(Type.TOPIC, name));
}
+
+ class ConfigurationControlIterator implements Iterator> {
+ private final long epoch;
+ private final Iterator>> iterator;
+
+ ConfigurationControlIterator(long epoch) {
+ this.epoch = epoch;
+ this.iterator = configData.entrySet(epoch).iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public List next() {
+ if (!hasNext()) throw new NoSuchElementException();
+ List records = new ArrayList<>();
+ Entry> entry = iterator.next();
+ ConfigResource resource = entry.getKey();
+ for (Entry configEntry : entry.getValue().entrySet(epoch)) {
+ records.add(new ApiMessageAndVersion(new ConfigRecord().
+ setResourceName(resource.name()).
+ setResourceType(resource.type().id()).
+ setName(configEntry.getKey()).
+ setValue(configEntry.getValue()), (short) 0));
+ }
+ return records;
+ }
+ }
+
+ ConfigurationControlIterator iterator(long epoch) {
+ return new ConfigurationControlIterator(epoch);
+ }
}
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 26394632e6a94..7892c1991ee9a 100644
--- a/metadata/src/main/java/org/apache/kafka/controller/Controller.java
+++ b/metadata/src/main/java/org/apache/kafka/controller/Controller.java
@@ -187,6 +187,14 @@ CompletableFuture