cmccabe commented on a change in pull request #10070: URL: https://github.com/apache/kafka/pull/10070#discussion_r578628736
########## File path: metadata/src/main/java/org/apache/kafka/controller/ClusterControlManager.java ########## @@ -0,0 +1,359 @@ +/* + * 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.Endpoint; +import org.apache.kafka.common.errors.DuplicateBrokerRegistrationException; +import org.apache.kafka.common.errors.StaleBrokerEpochException; +import org.apache.kafka.common.errors.UnsupportedVersionException; +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.UnfenceBrokerRecord; +import org.apache.kafka.common.metadata.UnregisterBrokerRecord; +import org.apache.kafka.common.security.auth.SecurityProtocol; +import org.apache.kafka.common.utils.LogContext; +import org.apache.kafka.common.utils.Time; +import org.apache.kafka.metadata.ApiMessageAndVersion; +import org.apache.kafka.metadata.BrokerRegistration; +import org.apache.kafka.metadata.BrokerRegistrationReply; +import org.apache.kafka.metadata.FeatureMapAndEpoch; +import org.apache.kafka.metadata.VersionRange; +import org.apache.kafka.timeline.SnapshotRegistry; +import org.apache.kafka.timeline.TimelineHashMap; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + + +public class ClusterControlManager { + class ReadyBrokersFuture { + private final CompletableFuture<Void> future; + private final int minBrokers; + + ReadyBrokersFuture(CompletableFuture<Void> future, int minBrokers) { + this.future = future; + this.minBrokers = minBrokers; + } + + boolean check() { + int numUnfenced = 0; + for (BrokerRegistration registration : brokerRegistrations.values()) { + if (!registration.fenced()) { + numUnfenced++; + } + if (numUnfenced >= minBrokers) { + return true; + } + } + return false; + } + } + + /** + * The SLF4J log context. + */ + private final LogContext logContext; + + /** + * The SLF4J log object. + */ + private final Logger log; + + /** + * The Kafka clock object to use. + */ + private final Time time; + + /** + * How long sessions should last, in nanoseconds. + */ + private final long sessionTimeoutNs; + + /** + * The replica placement policy to use. + */ + private final ReplicaPlacementPolicy placementPolicy; + + /** + * Maps broker IDs to broker registrations. + */ + private final TimelineHashMap<Integer, BrokerRegistration> brokerRegistrations; + + /** + * The broker heartbeat manager, or null if this controller is on standby. + */ + private BrokerHeartbeatManager heartbeatManager; + + /** + * A future which is completed as soon as we have the given number of brokers + * ready. + */ + private Optional<ReadyBrokersFuture> readyBrokersFuture; + + ClusterControlManager(LogContext logContext, + Time time, + SnapshotRegistry snapshotRegistry, + long sessionTimeoutNs, + ReplicaPlacementPolicy placementPolicy) { + this.logContext = logContext; + this.log = logContext.logger(ClusterControlManager.class); + this.time = time; + this.sessionTimeoutNs = sessionTimeoutNs; + this.placementPolicy = placementPolicy; + this.brokerRegistrations = new TimelineHashMap<>(snapshotRegistry, 0); + this.heartbeatManager = null; + this.readyBrokersFuture = Optional.empty(); + } + + /** + * Transition this ClusterControlManager to active. + */ + public void activate() { + heartbeatManager = new BrokerHeartbeatManager(logContext, time, sessionTimeoutNs); + for (BrokerRegistration registration : brokerRegistrations.values()) { + heartbeatManager.touch(registration.id(), registration.fenced(), -1); + } + } + + /** + * Transition this ClusterControlManager to standby. + */ + public void deactivate() { + heartbeatManager = null; + } + + Map<Integer, BrokerRegistration> brokerRegistrations() { + return brokerRegistrations; + } + + /** + * Process an incoming broker registration request. + */ + public ControllerResult<BrokerRegistrationReply> registerBroker( + BrokerRegistrationRequestData request, + long brokerEpoch, + FeatureMapAndEpoch finalizedFeatures) { + if (heartbeatManager == null) { + throw new RuntimeException("ClusterControlManager is not active."); + } + int brokerId = request.brokerId(); + BrokerRegistration existing = brokerRegistrations.get(brokerId); + if (existing != null) { + if (heartbeatManager.hasValidSession(brokerId)) { + if (!existing.incarnationId().equals(request.incarnationId())) { + throw new DuplicateBrokerRegistrationException("Another broker is " + + "registered with that broker id."); + } + } else { + if (!existing.incarnationId().equals(request.incarnationId())) { + // Remove any existing session for the old broker incarnation. + heartbeatManager.remove(brokerId); + existing = null; + } + } + } + + RegisterBrokerRecord record = new RegisterBrokerRecord().setBrokerId(brokerId). + setIncarnationId(request.incarnationId()). + setBrokerEpoch(brokerEpoch). + setRack(request.rack()); + for (BrokerRegistrationRequestData.Listener listener : request.listeners()) { + record.endPoints().add(new RegisterBrokerRecord.BrokerEndpoint(). + setHost(listener.host()). + setName(listener.name()). + setPort(listener.port()). + setSecurityProtocol(listener.securityProtocol())); + } + for (BrokerRegistrationRequestData.Feature feature : request.features()) { + Optional<VersionRange> finalized = finalizedFeatures.map().get(feature.name()); + if (finalized.isPresent()) { + if (!finalized.get().contains(new VersionRange(feature.minSupportedVersion(), + feature.maxSupportedVersion()))) { + throw new UnsupportedVersionException("Unable to register because " + + "the broker has an unsupported version of " + feature.name()); + } + } + record.features().add(new RegisterBrokerRecord.BrokerFeature(). + setName(feature.name()). + setMinSupportedVersion(feature.minSupportedVersion()). + setMaxSupportedVersion(feature.maxSupportedVersion())); + } + + if (existing == null) { + heartbeatManager.touch(brokerId, true, -1); + } else { + heartbeatManager.touch(brokerId, existing.fenced(), -1); + } + + List<ApiMessageAndVersion> records = new ArrayList<>(); + records.add(new ApiMessageAndVersion(record, (short) 0)); + return new ControllerResult<>(records, new BrokerRegistrationReply(brokerEpoch)); + } + + public ControllerResult<Void> decommissionBroker(int brokerId) { Review comment: Good catch. This has been superseded by `ReplicationControlManager#unregsiterBroker`. ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org