All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.kafka.common.requests.MetadataResponse Maven / Gradle / Ivy

There is a newer version: 3.7.0
Show newest version
/*
 * 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.common.requests;

import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.Node;
import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.message.MetadataResponseData;
import org.apache.kafka.common.message.MetadataResponseData.MetadataResponseBroker;
import org.apache.kafka.common.message.MetadataResponseData.MetadataResponsePartition;
import org.apache.kafka.common.message.MetadataResponseData.MetadataResponseTopic;
import org.apache.kafka.common.protocol.ApiKeys;
import org.apache.kafka.common.protocol.Errors;
import org.apache.kafka.common.protocol.types.Struct;
import org.apache.kafka.common.record.RecordBatch;
import org.apache.kafka.common.utils.Utils;

import java.nio.ByteBuffer;
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.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Possible topic-level error codes:
 *  UnknownTopic (3)
 *  LeaderNotAvailable (5)
 *  InvalidTopic (17)
 *  TopicAuthorizationFailed (29)

 * Possible partition-level error codes:
 *  LeaderNotAvailable (5)
 *  ReplicaNotAvailable (9)
 */
public class MetadataResponse extends AbstractResponse {
    public static final int NO_CONTROLLER_ID = -1;
    public static final int NO_LEADER_ID = -1;
    public static final int AUTHORIZED_OPERATIONS_OMITTED = Integer.MIN_VALUE;

    private final MetadataResponseData data;
    private volatile Holder holder;
    private final boolean hasReliableLeaderEpochs;

    public MetadataResponse(MetadataResponseData data) {
        this(data, true);
    }

    public MetadataResponse(Struct struct, short version) {
        // Prior to Kafka version 2.4 (which coincides with Metadata version 9), the broker
        // does not propagate leader epoch information accurately while a reassignment is in
        // progress. Relying on a stale epoch can lead to FENCED_LEADER_EPOCH errors which
        // can prevent consumption throughout the course of a reassignment. It is safer in
        // this case to revert to the behavior in previous protocol versions which checks
        // leader status only.
        this(new MetadataResponseData(struct, version), version >= 9);
    }

    private MetadataResponse(MetadataResponseData data, boolean hasReliableLeaderEpochs) {
        this.data = data;
        this.hasReliableLeaderEpochs = hasReliableLeaderEpochs;
    }

    @Override
    protected Struct toStruct(short version) {
        return data.toStruct(version);
    }

    @Override
    public int throttleTimeMs() {
        return data.throttleTimeMs();
    }

    /**
     * Get a map of the topics which had metadata errors
     * @return the map
     */
    public Map errors() {
        Map errors = new HashMap<>();
        for (MetadataResponseTopic metadata : data.topics()) {
            if (metadata.errorCode() != Errors.NONE.code())
                errors.put(metadata.name(), Errors.forCode(metadata.errorCode()));
        }
        return errors;
    }

    @Override
    public Map errorCounts() {
        Map errorCounts = new HashMap<>();
        data.topics().forEach(metadata ->
            updateErrorCounts(errorCounts, Errors.forCode(metadata.errorCode())));
        return errorCounts;
    }

    /**
     * Returns the set of topics with the specified error
     */
    public Set topicsByError(Errors error) {
        Set errorTopics = new HashSet<>();
        for (MetadataResponseTopic metadata : data.topics()) {
            if (metadata.errorCode() == error.code())
                errorTopics.add(metadata.name());
        }
        return errorTopics;
    }

    /**
     * Get a snapshot of the cluster metadata from this response
     * @return the cluster snapshot
     */
    public Cluster cluster() {
        Set internalTopics = new HashSet<>();
        List partitions = new ArrayList<>();

        for (TopicMetadata metadata : topicMetadata()) {
            if (metadata.error == Errors.NONE) {
                if (metadata.isInternal)
                    internalTopics.add(metadata.topic);
                for (PartitionMetadata partitionMetadata : metadata.partitionMetadata) {
                    partitions.add(toPartitionInfo(partitionMetadata, holder().brokers));
                }
            }
        }
        return new Cluster(data.clusterId(), brokers(), partitions, topicsByError(Errors.TOPIC_AUTHORIZATION_FAILED),
                topicsByError(Errors.INVALID_TOPIC_EXCEPTION), internalTopics, controller());
    }

    public static PartitionInfo toPartitionInfo(PartitionMetadata metadata, Map nodesById) {
        return new PartitionInfo(metadata.topic(),
                metadata.partition(),
                metadata.leaderId.map(nodesById::get).orElse(null),
                convertToNodeArray(metadata.replicaIds, nodesById),
                convertToNodeArray(metadata.inSyncReplicaIds, nodesById),
                convertToNodeArray(metadata.offlineReplicaIds, nodesById));
    }

    private static Node[] convertToNodeArray(List replicaIds, Map nodesById) {
        return replicaIds.stream().map(replicaId -> {
            Node node = nodesById.get(replicaId);
            if (node == null)
                return new Node(replicaId, "", -1);
            return node;
        }).toArray(Node[]::new);
    }

    /**
     * Returns a 32-bit bitfield to represent authorized operations for this topic.
     */
    public Optional topicAuthorizedOperations(String topicName) {
        MetadataResponseTopic topic = data.topics().find(topicName);
        if (topic == null)
            return Optional.empty();
        else
            return Optional.of(topic.topicAuthorizedOperations());
    }

    /**
     * Returns a 32-bit bitfield to represent authorized operations for this cluster.
     */
    public int clusterAuthorizedOperations() {
        return data.clusterAuthorizedOperations();
    }

    private Holder holder() {
        if (holder == null) {
            synchronized (data) {
                if (holder == null)
                    holder = new Holder(data);
            }
        }
        return holder;
    }

    /**
     * Get all brokers returned in metadata response
     * @return the brokers
     */
    public Collection brokers() {
        return holder().brokers.values();
    }

    public Map brokersById() {
        return holder().brokers;
    }

    /**
     * Get all topic metadata returned in the metadata response
     * @return the topicMetadata
     */
    public Collection topicMetadata() {
        return holder().topicMetadata;
    }

    /**
     * The controller node returned in metadata response
     * @return the controller node or null if it doesn't exist
     */
    public Node controller() {
        return holder().controller;
    }

    /**
     * The cluster identifier returned in the metadata response.
     * @return cluster identifier if it is present in the response, null otherwise.
     */
    public String clusterId() {
        return this.data.clusterId();
    }

    /**
     * Check whether the leader epochs returned from the response can be relied on
     * for epoch validation in Fetch, ListOffsets, and OffsetsForLeaderEpoch requests.
     * If not, then the client will not retain the leader epochs and hence will not
     * forward them in requests.
     *
     * @return true if the epoch can be used for validation
     */
    public boolean hasReliableLeaderEpochs() {
        return hasReliableLeaderEpochs;
    }

    public static MetadataResponse parse(ByteBuffer buffer, short version) {
        return new MetadataResponse(ApiKeys.METADATA.responseSchema(version).read(buffer), version);
    }

    public static class TopicMetadata {
        private final Errors error;
        private final String topic;
        private final boolean isInternal;
        private final List partitionMetadata;
        private int authorizedOperations;

        public TopicMetadata(Errors error,
                             String topic,
                             boolean isInternal,
                             List partitionMetadata,
                             int authorizedOperations) {
            this.error = error;
            this.topic = topic;
            this.isInternal = isInternal;
            this.partitionMetadata = partitionMetadata;
            this.authorizedOperations = authorizedOperations;
        }

        public TopicMetadata(Errors error,
                             String topic,
                             boolean isInternal,
                             List partitionMetadata) {
            this(error, topic, isInternal, partitionMetadata, AUTHORIZED_OPERATIONS_OMITTED);
        }

        public Errors error() {
            return error;
        }

        public String topic() {
            return topic;
        }

        public boolean isInternal() {
            return isInternal;
        }

        public List partitionMetadata() {
            return partitionMetadata;
        }

        public void authorizedOperations(int authorizedOperations) {
            this.authorizedOperations = authorizedOperations;
        }

        public int authorizedOperations() {
            return authorizedOperations;
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            final TopicMetadata that = (TopicMetadata) o;
            return isInternal == that.isInternal &&
                error == that.error &&
                Objects.equals(topic, that.topic) &&
                Objects.equals(partitionMetadata, that.partitionMetadata) &&
                Objects.equals(authorizedOperations, that.authorizedOperations);
        }

        @Override
        public int hashCode() {
            return Objects.hash(error, topic, isInternal, partitionMetadata, authorizedOperations);
        }

        @Override
        public String toString() {
            return "TopicMetadata{" +
                "error=" + error +
                ", topic='" + topic + '\'' +
                ", isInternal=" + isInternal +
                ", partitionMetadata=" + partitionMetadata +
                ", authorizedOperations=" + authorizedOperations +
                '}';
        }
    }

    // This is used to describe per-partition state in the MetadataResponse
    public static class PartitionMetadata {
        public final TopicPartition topicPartition;
        public final Errors error;
        public final Optional leaderId;
        public final Optional leaderEpoch;
        public final List replicaIds;
        public final List inSyncReplicaIds;
        public final List offlineReplicaIds;

        public PartitionMetadata(Errors error,
                                 TopicPartition topicPartition,
                                 Optional leaderId,
                                 Optional leaderEpoch,
                                 List replicaIds,
                                 List inSyncReplicaIds,
                                 List offlineReplicaIds) {
            this.error = error;
            this.topicPartition = topicPartition;
            this.leaderId = leaderId;
            this.leaderEpoch = leaderEpoch;
            this.replicaIds = replicaIds;
            this.inSyncReplicaIds = inSyncReplicaIds;
            this.offlineReplicaIds = offlineReplicaIds;
        }

        public int partition() {
            return topicPartition.partition();
        }

        public String topic() {
            return topicPartition.topic();
        }

        public PartitionMetadata withoutLeaderEpoch() {
            return new PartitionMetadata(error,
                    topicPartition,
                    leaderId,
                    Optional.empty(),
                    replicaIds,
                    inSyncReplicaIds,
                    offlineReplicaIds);
        }

        @Override
        public String toString() {
            return "PartitionMetadata(" +
                    "error=" + error +
                    ", partition=" + topicPartition +
                    ", leader=" + leaderId +
                    ", leaderEpoch=" + leaderEpoch +
                    ", replicas=" + Utils.join(replicaIds, ",") +
                    ", isr=" + Utils.join(inSyncReplicaIds, ",") +
                    ", offlineReplicas=" + Utils.join(offlineReplicaIds, ",") + ')';
        }
    }

    private static class Holder {
        private final Map brokers;
        private final Node controller;
        private final Collection topicMetadata;

        Holder(MetadataResponseData data) {
            this.brokers = Collections.unmodifiableMap(createBrokers(data));
            this.topicMetadata = createTopicMetadata(data);
            this.controller = brokers.get(data.controllerId());
        }

        private Map createBrokers(MetadataResponseData data) {
            return data.brokers().valuesList().stream().map(b -> new Node(b.nodeId(), b.host(), b.port(), b.rack()))
                    .collect(Collectors.toMap(Node::id, Function.identity()));
        }

        private Collection createTopicMetadata(MetadataResponseData data) {
            List topicMetadataList = new ArrayList<>();
            for (MetadataResponseTopic topicMetadata : data.topics()) {
                Errors topicError = Errors.forCode(topicMetadata.errorCode());
                String topic = topicMetadata.name();
                boolean isInternal = topicMetadata.isInternal();
                List partitionMetadataList = new ArrayList<>();

                for (MetadataResponsePartition partitionMetadata : topicMetadata.partitions()) {
                    Errors partitionError = Errors.forCode(partitionMetadata.errorCode());
                    int partitionIndex = partitionMetadata.partitionIndex();

                    int leaderId = partitionMetadata.leaderId();
                    Optional leaderIdOpt = leaderId < 0 ? Optional.empty() : Optional.of(leaderId);

                    Optional leaderEpoch = RequestUtils.getLeaderEpoch(partitionMetadata.leaderEpoch());
                    TopicPartition topicPartition = new TopicPartition(topic, partitionIndex);
                    partitionMetadataList.add(new PartitionMetadata(partitionError, topicPartition, leaderIdOpt,
                            leaderEpoch, partitionMetadata.replicaNodes(), partitionMetadata.isrNodes(),
                            partitionMetadata.offlineReplicas()));
                }

                topicMetadataList.add(new TopicMetadata(topicError, topic, isInternal, partitionMetadataList,
                        topicMetadata.topicAuthorizedOperations()));
            }
            return topicMetadataList;
        }

    }

    public static MetadataResponse prepareResponse(int throttleTimeMs, Collection brokers, String clusterId,
                                                   int controllerId, List topicMetadataList,
                                                   int clusterAuthorizedOperations,
                                                   short responseVersion) {
        MetadataResponseData responseData = new MetadataResponseData();
        responseData.setThrottleTimeMs(throttleTimeMs);
        brokers.forEach(broker ->
            responseData.brokers().add(new MetadataResponseBroker()
                .setNodeId(broker.id())
                .setHost(broker.host())
                .setPort(broker.port())
                .setRack(broker.rack()))
        );

        responseData.setClusterId(clusterId);
        responseData.setControllerId(controllerId);
        responseData.setClusterAuthorizedOperations(clusterAuthorizedOperations);

        topicMetadataList.forEach(topicMetadata -> {
            MetadataResponseTopic metadataResponseTopic = new MetadataResponseTopic();
            metadataResponseTopic
                .setErrorCode(topicMetadata.error.code())
                .setName(topicMetadata.topic)
                .setIsInternal(topicMetadata.isInternal)
                .setTopicAuthorizedOperations(topicMetadata.authorizedOperations);

            for (PartitionMetadata partitionMetadata : topicMetadata.partitionMetadata) {
                metadataResponseTopic.partitions().add(new MetadataResponsePartition()
                    .setErrorCode(partitionMetadata.error.code())
                    .setPartitionIndex(partitionMetadata.partition())
                    .setLeaderId(partitionMetadata.leaderId.orElse(NO_LEADER_ID))
                    .setLeaderEpoch(partitionMetadata.leaderEpoch.orElse(RecordBatch.NO_PARTITION_LEADER_EPOCH))
                    .setReplicaNodes(partitionMetadata.replicaIds)
                    .setIsrNodes(partitionMetadata.inSyncReplicaIds)
                    .setOfflineReplicas(partitionMetadata.offlineReplicaIds));
            }
            responseData.topics().add(metadataResponseTopic);
        });
        return new MetadataResponse(responseData.toStruct(responseVersion), responseVersion);
    }

    public static MetadataResponse prepareResponse(int throttleTimeMs,
                                                   Collection brokers,
                                                   String clusterId,
                                                   int controllerId,
                                                   List topicMetadataList,
                                                   short responseVersion) {
        return prepareResponse(throttleTimeMs, brokers, clusterId, controllerId, topicMetadataList,
                MetadataResponse.AUTHORIZED_OPERATIONS_OMITTED, responseVersion);
    }

    public static MetadataResponse prepareResponse(Collection brokers,
                                                   String clusterId,
                                                   int controllerId,
                                                   List topicMetadata,
                                                   short responseVersion) {
        return prepareResponse(AbstractResponse.DEFAULT_THROTTLE_TIME, brokers, clusterId, controllerId,
            topicMetadata, responseVersion);
    }

    public static MetadataResponse prepareResponse(Collection brokers,
                                                   String clusterId,
                                                   int controllerId,
                                                   List topicMetadata) {
        return prepareResponse(AbstractResponse.DEFAULT_THROTTLE_TIME, brokers, clusterId, controllerId,
            topicMetadata, ApiKeys.METADATA.latestVersion());
    }

    public static MetadataResponse prepareResponse(int throttleTimeMs,
                                                   List topicMetadataList,
                                                   Collection brokers,
                                                   String clusterId,
                                                   int controllerId,
                                                   int clusterAuthorizedOperations) {
        MetadataResponseData responseData = new MetadataResponseData();
        responseData.setThrottleTimeMs(throttleTimeMs);
        brokers.forEach(broker ->
            responseData.brokers().add(new MetadataResponseBroker()
                .setNodeId(broker.id())
                .setHost(broker.host())
                .setPort(broker.port())
                .setRack(broker.rack()))
        );

        responseData.setClusterId(clusterId);
        responseData.setControllerId(controllerId);
        responseData.setClusterAuthorizedOperations(clusterAuthorizedOperations);

        topicMetadataList.forEach(topicMetadata -> responseData.topics().add(topicMetadata));
        return new MetadataResponse(responseData);
    }

    @Override
    public boolean shouldClientThrottle(short version) {
        return version >= 6;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy