/* * 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; import org.apache.kafka.common.utils.Utils; import java.net.InetSocketAddress; 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.Set; /** * A representation of a subset of the nodes, topics, and partitions in the Kafka cluster. */ public final class Cluster { private final boolean isBootstrapConfigured; private final List<Node> nodes; private final Set<String> unauthorizedTopics; private final Set<String> internalTopics; private final Node controller; private final Map<TopicPartition, PartitionInfo> partitionsByTopicPartition; private final Map<String, List<PartitionInfo>> partitionsByTopic; private final Map<String, List<PartitionInfo>> availablePartitionsByTopic; private final Map<Integer, List<PartitionInfo>> partitionsByNode; private final Map<Integer, Node> nodesById; private final ClusterResource clusterResource; /** * Create a new cluster with the given id, nodes and partitions * @param nodes The nodes in the cluster * @param partitions Information about a subset of the topic-partitions this cluster hosts */ public Cluster(String clusterId, Collection<Node> nodes, Collection<PartitionInfo> partitions, Set<String> unauthorizedTopics, Set<String> internalTopics) { this(clusterId, false, nodes, partitions, unauthorizedTopics, internalTopics, null); } /** * Create a new cluster with the given id, nodes and partitions * @param nodes The nodes in the cluster * @param partitions Information about a subset of the topic-partitions this cluster hosts */ public Cluster(String clusterId, Collection<Node> nodes, Collection<PartitionInfo> partitions, Set<String> unauthorizedTopics, Set<String> internalTopics, Node controller) { this(clusterId, false, nodes, partitions, unauthorizedTopics, internalTopics, controller); } private Cluster(String clusterId, boolean isBootstrapConfigured, Collection<Node> nodes, Collection<PartitionInfo> partitions, Set<String> unauthorizedTopics, Set<String> internalTopics, Node controller) { this.isBootstrapConfigured = isBootstrapConfigured; this.clusterResource = new ClusterResource(clusterId); // make a randomized, unmodifiable copy of the nodes List<Node> copy = new ArrayList<>(nodes); Collections.shuffle(copy); this.nodes = Collections.unmodifiableList(copy); this.nodesById = new HashMap<>(); for (Node node : nodes) this.nodesById.put(node.id(), node); // index the partitions by topic/partition for quick lookup this.partitionsByTopicPartition = new HashMap<>(partitions.size()); for (PartitionInfo p : partitions) this.partitionsByTopicPartition.put(new TopicPartition(p.topic(), p.partition()), p); // index the partitions by topic and node respectively, and make the lists // unmodifiable so we can hand them out in user-facing apis without risk // of the client modifying the contents HashMap<String, List<PartitionInfo>> partsForTopic = new HashMap<>(); HashMap<Integer, List<PartitionInfo>> partsForNode = new HashMap<>(); for (Node n : this.nodes) { partsForNode.put(n.id(), new ArrayList<PartitionInfo>()); } for (PartitionInfo p : partitions) { if (!partsForTopic.containsKey(p.topic())) partsForTopic.put(p.topic(), new ArrayList<PartitionInfo>()); List<PartitionInfo> psTopic = partsForTopic.get(p.topic()); psTopic.add(p); if (p.leader() != null) { List<PartitionInfo> psNode = Utils.notNull(partsForNode.get(p.leader().id())); psNode.add(p); } } this.partitionsByTopic = new HashMap<>(partsForTopic.size()); this.availablePartitionsByTopic = new HashMap<>(partsForTopic.size()); for (Map.Entry<String, List<PartitionInfo>> entry : partsForTopic.entrySet()) { String topic = entry.getKey(); List<PartitionInfo> partitionList = entry.getValue(); this.partitionsByTopic.put(topic, Collections.unmodifiableList(partitionList)); List<PartitionInfo> availablePartitions = new ArrayList<>(); for (PartitionInfo part : partitionList) { if (part.leader() != null) availablePartitions.add(part); } this.availablePartitionsByTopic.put(topic, Collections.unmodifiableList(availablePartitions)); } this.partitionsByNode = new HashMap<>(partsForNode.size()); for (Map.Entry<Integer, List<PartitionInfo>> entry : partsForNode.entrySet()) this.partitionsByNode.put(entry.getKey(), Collections.unmodifiableList(entry.getValue())); this.unauthorizedTopics = Collections.unmodifiableSet(unauthorizedTopics); this.internalTopics = Collections.unmodifiableSet(internalTopics); this.controller = controller; } /** * Create an empty cluster instance with no nodes and no topic-partitions. */ public static Cluster empty() { return new Cluster(null, new ArrayList<Node>(0), new ArrayList<PartitionInfo>(0), Collections.<String>emptySet(), Collections.<String>emptySet(), null); } /** * Create a "bootstrap" cluster using the given list of host/ports * @param addresses The addresses * @return A cluster for these hosts/ports */ public static Cluster bootstrap(List<InetSocketAddress> addresses) { List<Node> nodes = new ArrayList<>(); int nodeId = -1; for (InetSocketAddress address : addresses) nodes.add(new Node(nodeId--, address.getHostString(), address.getPort())); return new Cluster(null, true, nodes, new ArrayList<PartitionInfo>(0), Collections.<String>emptySet(), Collections.<String>emptySet(), null); } /** * Return a copy of this cluster combined with `partitions`. */ public Cluster withPartitions(Map<TopicPartition, PartitionInfo> partitions) { Map<TopicPartition, PartitionInfo> combinedPartitions = new HashMap<>(this.partitionsByTopicPartition); combinedPartitions.putAll(partitions); return new Cluster(clusterResource.clusterId(), this.nodes, combinedPartitions.values(), new HashSet<>(this.unauthorizedTopics), new HashSet<>(this.internalTopics), this.controller); } /** * @return The known set of nodes */ public List<Node> nodes() { return this.nodes; } /** * Get the node by the node id (or null if no such node exists) * @param id The id of the node * @return The node, or null if no such node exists */ public Node nodeById(int id) { return this.nodesById.get(id); } /** * Get the current leader for the given topic-partition * @param topicPartition The topic and partition we want to know the leader for * @return The node that is the leader for this topic-partition, or null if there is currently no leader */ public Node leaderFor(TopicPartition topicPartition) { PartitionInfo info = partitionsByTopicPartition.get(topicPartition); if (info == null) return null; else return info.leader(); } /** * Get the metadata for the specified partition * @param topicPartition The topic and partition to fetch info for * @return The metadata about the given topic and partition */ public PartitionInfo partition(TopicPartition topicPartition) { return partitionsByTopicPartition.get(topicPartition); } /** * Get the list of partitions for this topic * @param topic The topic name * @return A list of partitions */ public List<PartitionInfo> partitionsForTopic(String topic) { List<PartitionInfo> parts = this.partitionsByTopic.get(topic); return (parts == null) ? Collections.<PartitionInfo>emptyList() : parts; } /** * Get the number of partitions for the given topic * @param topic The topic to get the number of partitions for * @return The number of partitions or null if there is no corresponding metadata */ public Integer partitionCountForTopic(String topic) { List<PartitionInfo> partitions = this.partitionsByTopic.get(topic); return partitions == null ? null : partitions.size(); } /** * Get the list of available partitions for this topic * @param topic The topic name * @return A list of partitions */ public List<PartitionInfo> availablePartitionsForTopic(String topic) { List<PartitionInfo> parts = this.availablePartitionsByTopic.get(topic); return (parts == null) ? Collections.<PartitionInfo>emptyList() : parts; } /** * Get the list of partitions whose leader is this node * @param nodeId The node id * @return A list of partitions */ public List<PartitionInfo> partitionsForNode(int nodeId) { List<PartitionInfo> parts = this.partitionsByNode.get(nodeId); return (parts == null) ? Collections.<PartitionInfo>emptyList() : parts; } /** * Get all topics. * @return a set of all topics */ public Set<String> topics() { return this.partitionsByTopic.keySet(); } public Set<String> unauthorizedTopics() { return unauthorizedTopics; } public Set<String> internalTopics() { return internalTopics; } public boolean isBootstrapConfigured() { return isBootstrapConfigured; } public ClusterResource clusterResource() { return clusterResource; } public Node controller() { return controller; } @Override public String toString() { return "Cluster(id = " + clusterResource.clusterId() + ", nodes = " + this.nodes + ", partitions = " + this.partitionsByTopicPartition.values() + ")"; } }