cmccabe commented on a change in pull request #10070:
URL: https://github.com/apache/kafka/pull/10070#discussion_r577187004



##########
File path: 
metadata/src/main/java/org/apache/kafka/controller/ClusterControlManager.java
##########
@@ -0,0 +1,456 @@
+/*
+ * 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.BrokerHeartbeatRequestData;
+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.BrokerHeartbeatReply;
+import org.apache.kafka.metadata.BrokerRegistration;
+import org.apache.kafka.metadata.BrokerRegistrationReply;
+import org.apache.kafka.metadata.FeatureManager;
+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.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+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;
+    }
+
+    // VisibleForTesting
+    Map<Integer, BrokerRegistration> brokerRegistrations() {
+        return brokerRegistrations;
+    }
+
+    /**
+     * Process an incoming broker registration request.
+     */
+    public ControllerResult<BrokerRegistrationReply> registerBroker(
+            BrokerRegistrationRequestData request, long brokerEpoch,
+            FeatureManager.FinalizedFeaturesAndEpoch 
finalizedFeaturesAndEpoch) {
+        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()) {
+            VersionRange supported = 
finalizedFeaturesAndEpoch.finalizedFeatures().
+                getOrDefault(feature.name(), VersionRange.ALL);
+            if (!supported.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) {
+        if (heartbeatManager == null) {
+            throw new RuntimeException("ClusterControlManager is not active.");
+        }
+        BrokerRegistration existing = brokerRegistrations.get(brokerId);
+        if (existing == null) {
+            return new ControllerResult<>(new ArrayList<>(), null);
+        }
+        List<ApiMessageAndVersion> records = new ArrayList<>();
+        records.add(new ApiMessageAndVersion(
+            new UnregisterBrokerRecord().
+                setBrokerId(brokerId).
+                setBrokerEpoch(existing.epoch()),
+            (short) 0));
+        heartbeatManager.remove(brokerId);
+        return new ControllerResult<>(records, null);
+    }
+
+    public ControllerResult<BrokerHeartbeatReply> processBrokerHeartbeat(
+            BrokerHeartbeatRequestData request,
+            long lastCommittedOffset,
+            boolean movingLeadersForShutDown) {
+        if (heartbeatManager == null) {
+            throw new RuntimeException("ClusterControlManager is not active.");
+        }
+        int brokerId = request.brokerId();
+        BrokerRegistration registration = brokerRegistrations.get(brokerId);
+        if (registration == null) {
+            throw new StaleBrokerEpochException("No registration found for 
broker " +
+                request.brokerId());
+        }
+        if (request.brokerEpoch() != registration.epoch()) {
+            throw new StaleBrokerEpochException("Expected broker epoch " +
+                request.brokerEpoch() + "; got epoch " + registration.epoch());
+        }
+        boolean isCaughtUp = request.currentMetadataOffset() >= 
lastCommittedOffset;
+        List<ApiMessageAndVersion> records = new ArrayList<>();
+        boolean isFenced = registration.fenced();
+        boolean shouldShutdown = false;
+        if (isFenced) {
+            if (request.wantShutDown()) {
+                // If the broker is fenced, and requests a shutdown, do it 
immediately.
+                isFenced = true;
+                shouldShutdown = true;
+            } else if (isCaughtUp && !request.wantFence()) {
+                records.add(new ApiMessageAndVersion(new UnfenceBrokerRecord().
+                    setId(brokerId).setEpoch(request.brokerEpoch()), (short) 
0));
+                isFenced = false;
+                shouldShutdown = false;
+            }
+        } else {
+            if (request.wantFence()) {
+                records.add(new ApiMessageAndVersion(new FenceBrokerRecord().
+                    setId(brokerId).setEpoch(request.brokerEpoch()), (short) 
0));
+                isFenced = true;
+                shouldShutdown = request.wantShutDown();
+            } else {
+                if (request.wantShutDown()) {
+                    heartbeatManager.beginBrokerShutDown(request.brokerId(), 
movingLeadersForShutDown);
+                }
+                if (heartbeatManager.shouldShutDown(request.brokerId())) {
+                    isFenced = true;
+                    shouldShutdown = true;
+                    records.add(new ApiMessageAndVersion(new 
FenceBrokerRecord().

Review comment:
       I will improve the logging a bit here.  I agree that we should log when 
a broker shuts down or is fenced, since those are major events.




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


Reply via email to