/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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.elasticsearch.cluster.node; import com.carrotsearch.hppc.ObjectHashSet; import com.carrotsearch.hppc.cursors.ObjectCursor; import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import org.elasticsearch.Version; import org.elasticsearch.cluster.AbstractDiffable; import org.elasticsearch.cluster.Diff; import org.elasticsearch.common.Booleans; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.transport.TransportAddress; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; /** * This class holds all {@link DiscoveryNode} in the cluster and provides convenience methods to * access, modify merge / diff discovery nodes. */ public class DiscoveryNodes extends AbstractDiffable<DiscoveryNodes> implements Iterable<DiscoveryNode> { public static final DiscoveryNodes EMPTY_NODES = builder().build(); private final ImmutableOpenMap<String, DiscoveryNode> nodes; private final ImmutableOpenMap<String, DiscoveryNode> dataNodes; private final ImmutableOpenMap<String, DiscoveryNode> masterNodes; private final ImmutableOpenMap<String, DiscoveryNode> ingestNodes; private final String masterNodeId; private final String localNodeId; private final Version minNonClientNodeVersion; private final Version maxNodeVersion; private final Version minNodeVersion; private DiscoveryNodes(ImmutableOpenMap<String, DiscoveryNode> nodes, ImmutableOpenMap<String, DiscoveryNode> dataNodes, ImmutableOpenMap<String, DiscoveryNode> masterNodes, ImmutableOpenMap<String, DiscoveryNode> ingestNodes, String masterNodeId, String localNodeId, Version minNonClientNodeVersion, Version maxNodeVersion, Version minNodeVersion) { this.nodes = nodes; this.dataNodes = dataNodes; this.masterNodes = masterNodes; this.ingestNodes = ingestNodes; this.masterNodeId = masterNodeId; this.localNodeId = localNodeId; this.minNonClientNodeVersion = minNonClientNodeVersion; this.minNodeVersion = minNodeVersion; this.maxNodeVersion = maxNodeVersion; } @Override public Iterator<DiscoveryNode> iterator() { return nodes.valuesIt(); } /** * Returns <tt>true</tt> if the local node is the elected master node. */ public boolean isLocalNodeElectedMaster() { if (localNodeId == null) { // we don't know yet the local node id, return false return false; } return localNodeId.equals(masterNodeId); } /** * Get the number of known nodes * * @return number of nodes */ public int getSize() { return nodes.size(); } /** * Get a {@link Map} of the discovered nodes arranged by their ids * * @return {@link Map} of the discovered nodes arranged by their ids */ public ImmutableOpenMap<String, DiscoveryNode> getNodes() { return this.nodes; } /** * Get a {@link Map} of the discovered data nodes arranged by their ids * * @return {@link Map} of the discovered data nodes arranged by their ids */ public ImmutableOpenMap<String, DiscoveryNode> getDataNodes() { return this.dataNodes; } /** * Get a {@link Map} of the discovered master nodes arranged by their ids * * @return {@link Map} of the discovered master nodes arranged by their ids */ public ImmutableOpenMap<String, DiscoveryNode> getMasterNodes() { return this.masterNodes; } /** * @return All the ingest nodes arranged by their ids */ public ImmutableOpenMap<String, DiscoveryNode> getIngestNodes() { return ingestNodes; } /** * Get a {@link Map} of the discovered master and data nodes arranged by their ids * * @return {@link Map} of the discovered master and data nodes arranged by their ids */ public ImmutableOpenMap<String, DiscoveryNode> getMasterAndDataNodes() { ImmutableOpenMap.Builder<String, DiscoveryNode> nodes = ImmutableOpenMap.builder(dataNodes); nodes.putAll(masterNodes); return nodes.build(); } /** * Get a node by its id * * @param nodeId id of the wanted node * @return wanted node if it exists. Otherwise <code>null</code> */ public DiscoveryNode get(String nodeId) { return nodes.get(nodeId); } /** * Determine if a given node id exists * * @param nodeId id of the node which existence should be verified * @return <code>true</code> if the node exists. Otherwise <code>false</code> */ public boolean nodeExists(String nodeId) { return nodes.containsKey(nodeId); } /** * Determine if a given node exists * * @param node of the node which existence should be verified * @return <code>true</code> if the node exists. Otherwise <code>false</code> */ public boolean nodeExists(DiscoveryNode node) { DiscoveryNode existing = nodes.get(node.getId()); return existing != null && existing.equals(node); } /** * Get the id of the master node * * @return id of the master */ public String getMasterNodeId() { return this.masterNodeId; } /** * Get the id of the local node * * @return id of the local node */ public String getLocalNodeId() { return this.localNodeId; } /** * Get the local node * * @return local node */ public DiscoveryNode getLocalNode() { return nodes.get(localNodeId); } /** * Get the master node * * @return master node */ public DiscoveryNode getMasterNode() { return nodes.get(masterNodeId); } /** * Get a node by its address * * @param address {@link TransportAddress} of the wanted node * @return node identified by the given address or <code>null</code> if no such node exists */ public DiscoveryNode findByAddress(TransportAddress address) { for (ObjectCursor<DiscoveryNode> cursor : nodes.values()) { DiscoveryNode node = cursor.value; if (node.getAddress().equals(address)) { return node; } } return null; } public boolean isAllNodes(String... nodesIds) { return nodesIds == null || nodesIds.length == 0 || (nodesIds.length == 1 && nodesIds[0].equals("_all")); } /** * Returns the version of the node with the oldest version in the cluster that is not a client node * * @return the oldest version in the cluster */ public Version getSmallestNonClientNodeVersion() { return minNonClientNodeVersion; } /** * Returns the version of the node with the oldest version in the cluster. * * @return the oldest version in the cluster */ public Version getMinNodeVersion() { return minNodeVersion; } /** * Returns the version of the node with the youngest version in the cluster * * @return the oldest version in the cluster */ public Version getMaxNodeVersion() { return maxNodeVersion; } /** * Resolve a node with a given id * * @param node id of the node to discover * @return discovered node matching the given id * @throws IllegalArgumentException if more than one node matches the request or no nodes have been resolved */ public DiscoveryNode resolveNode(String node) { String[] resolvedNodeIds = resolveNodes(node); if (resolvedNodeIds.length > 1) { throw new IllegalArgumentException("resolved [" + node + "] into [" + resolvedNodeIds.length + "] nodes, where expected to be resolved to a single node"); } if (resolvedNodeIds.length == 0) { throw new IllegalArgumentException("failed to resolve [" + node + "], no matching nodes"); } return nodes.get(resolvedNodeIds[0]); } /** * resolves a set of node "descriptions" to concrete and existing node ids. "descriptions" can be (resolved in this order): * - "_local" or "_master" for the relevant nodes * - a node id * - a wild card pattern that will be matched against node names * - a "attr:value" pattern, where attr can be a node role (master, data, ingest etc.) in which case the value can be true of false * or a generic node attribute name in which case value will be treated as a wildcard and matched against the node attribute values. */ public String[] resolveNodes(String... nodes) { if (isAllNodes(nodes)) { int index = 0; nodes = new String[this.nodes.size()]; for (DiscoveryNode node : this) { nodes[index++] = node.getId(); } return nodes; } else { ObjectHashSet<String> resolvedNodesIds = new ObjectHashSet<>(nodes.length); for (String nodeId : nodes) { if (nodeId.equals("_local")) { String localNodeId = getLocalNodeId(); if (localNodeId != null) { resolvedNodesIds.add(localNodeId); } } else if (nodeId.equals("_master")) { String masterNodeId = getMasterNodeId(); if (masterNodeId != null) { resolvedNodesIds.add(masterNodeId); } } else if (nodeExists(nodeId)) { resolvedNodesIds.add(nodeId); } else { // not a node id, try and search by name for (DiscoveryNode node : this) { if (Regex.simpleMatch(nodeId, node.getName())) { resolvedNodesIds.add(node.getId()); } } for (DiscoveryNode node : this) { if (Regex.simpleMatch(nodeId, node.getHostAddress())) { resolvedNodesIds.add(node.getId()); } else if (Regex.simpleMatch(nodeId, node.getHostName())) { resolvedNodesIds.add(node.getId()); } } int index = nodeId.indexOf(':'); if (index != -1) { String matchAttrName = nodeId.substring(0, index); String matchAttrValue = nodeId.substring(index + 1); if (DiscoveryNode.Role.DATA.getRoleName().equals(matchAttrName)) { if (Booleans.parseBoolean(matchAttrValue, true)) { resolvedNodesIds.addAll(dataNodes.keys()); } else { resolvedNodesIds.removeAll(dataNodes.keys()); } } else if (DiscoveryNode.Role.MASTER.getRoleName().equals(matchAttrName)) { if (Booleans.parseBoolean(matchAttrValue, true)) { resolvedNodesIds.addAll(masterNodes.keys()); } else { resolvedNodesIds.removeAll(masterNodes.keys()); } } else if (DiscoveryNode.Role.INGEST.getRoleName().equals(matchAttrName)) { if (Booleans.parseBoolean(matchAttrValue, true)) { resolvedNodesIds.addAll(ingestNodes.keys()); } else { resolvedNodesIds.removeAll(ingestNodes.keys()); } } else { for (DiscoveryNode node : this) { for (Map.Entry<String, String> entry : node.getAttributes().entrySet()) { String attrName = entry.getKey(); String attrValue = entry.getValue(); if (Regex.simpleMatch(matchAttrName, attrName) && Regex.simpleMatch(matchAttrValue, attrValue)) { resolvedNodesIds.add(node.getId()); } } } } } } } return resolvedNodesIds.toArray(String.class); } } public DiscoveryNodes newNode(DiscoveryNode node) { return new Builder(this).add(node).build(); } /** * Returns the changes comparing this nodes to the provided nodes. */ public Delta delta(DiscoveryNodes other) { List<DiscoveryNode> removed = new ArrayList<>(); List<DiscoveryNode> added = new ArrayList<>(); for (DiscoveryNode node : other) { if (!this.nodeExists(node)) { removed.add(node); } } for (DiscoveryNode node : this) { if (!other.nodeExists(node)) { added.add(node); } } DiscoveryNode previousMasterNode = null; DiscoveryNode newMasterNode = null; if (masterNodeId != null) { if (other.masterNodeId == null || !other.masterNodeId.equals(masterNodeId)) { previousMasterNode = other.getMasterNode(); newMasterNode = getMasterNode(); } } return new Delta(previousMasterNode, newMasterNode, localNodeId, Collections.unmodifiableList(removed), Collections.unmodifiableList(added)); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("nodes: \n"); for (DiscoveryNode node : this) { sb.append(" ").append(node); if (node == getLocalNode()) { sb.append(", local"); } if (node == getMasterNode()) { sb.append(", master"); } sb.append("\n"); } return sb.toString(); } public static class Delta { private final String localNodeId; private final DiscoveryNode previousMasterNode; private final DiscoveryNode newMasterNode; private final List<DiscoveryNode> removed; private final List<DiscoveryNode> added; private Delta(@Nullable DiscoveryNode previousMasterNode, @Nullable DiscoveryNode newMasterNode, String localNodeId, List<DiscoveryNode> removed, List<DiscoveryNode> added) { this.previousMasterNode = previousMasterNode; this.newMasterNode = newMasterNode; this.localNodeId = localNodeId; this.removed = removed; this.added = added; } public boolean hasChanges() { return masterNodeChanged() || !removed.isEmpty() || !added.isEmpty(); } public boolean masterNodeChanged() { return newMasterNode != null; } public DiscoveryNode previousMasterNode() { return previousMasterNode; } public DiscoveryNode newMasterNode() { return newMasterNode; } public boolean removed() { return !removed.isEmpty(); } public List<DiscoveryNode> removedNodes() { return removed; } public boolean added() { return !added.isEmpty(); } public List<DiscoveryNode> addedNodes() { return added; } public String shortSummary() { StringBuilder sb = new StringBuilder(); if (!removed() && masterNodeChanged()) { if (newMasterNode.getId().equals(localNodeId)) { // we are the master, no nodes we removed, we are actually the first master sb.append("new_master ").append(newMasterNode()); } else { // we are not the master, so we just got this event. No nodes were removed, so its not a *new* master sb.append("detected_master ").append(newMasterNode()); } } else { if (masterNodeChanged()) { sb.append("master {new ").append(newMasterNode()); if (previousMasterNode() != null) { sb.append(", previous ").append(previousMasterNode()); } sb.append("}"); } if (removed()) { if (masterNodeChanged()) { sb.append(", "); } sb.append("removed {"); for (DiscoveryNode node : removedNodes()) { sb.append(node).append(','); } sb.append("}"); } } if (added()) { // don't print if there is one added, and it is us if (!(addedNodes().size() == 1 && addedNodes().get(0).getId().equals(localNodeId))) { if (removed() || masterNodeChanged()) { sb.append(", "); } sb.append("added {"); for (DiscoveryNode node : addedNodes()) { if (!node.getId().equals(localNodeId)) { // don't print ourself sb.append(node).append(','); } } sb.append("}"); } } return sb.toString(); } } @Override public void writeTo(StreamOutput out) throws IOException { if (masterNodeId == null) { out.writeBoolean(false); } else { out.writeBoolean(true); out.writeString(masterNodeId); } out.writeVInt(nodes.size()); for (DiscoveryNode node : this) { node.writeTo(out); } } public static DiscoveryNodes readFrom(StreamInput in, DiscoveryNode localNode) throws IOException { Builder builder = new Builder(); if (in.readBoolean()) { builder.masterNodeId(in.readString()); } if (localNode != null) { builder.localNodeId(localNode.getId()); } int size = in.readVInt(); for (int i = 0; i < size; i++) { DiscoveryNode node = new DiscoveryNode(in); if (localNode != null && node.getId().equals(localNode.getId())) { // reuse the same instance of our address and local node id for faster equality node = localNode; } // some one already built this and validated it's OK, skip the n2 scans assert builder.validateAdd(node) == null : "building disco nodes from network doesn't pass preflight: " + builder.validateAdd(node); builder.putUnsafe(node); } return builder.build(); } public static Diff<DiscoveryNodes> readDiffFrom(StreamInput in, DiscoveryNode localNode) throws IOException { return AbstractDiffable.readDiffFrom(in1 -> readFrom(in1, localNode), in); } public static Builder builder() { return new Builder(); } public static Builder builder(DiscoveryNodes nodes) { return new Builder(nodes); } public static class Builder { private final ImmutableOpenMap.Builder<String, DiscoveryNode> nodes; private String masterNodeId; private String localNodeId; public Builder() { nodes = ImmutableOpenMap.builder(); } public Builder(DiscoveryNodes nodes) { this.masterNodeId = nodes.getMasterNodeId(); this.localNodeId = nodes.getLocalNodeId(); this.nodes = ImmutableOpenMap.builder(nodes.getNodes()); } /** * adds a disco node to the builder. Will throw an {@link IllegalArgumentException} if * the supplied node doesn't pass the pre-flight checks performed by {@link #validateAdd(DiscoveryNode)} */ public Builder add(DiscoveryNode node) { final String preflight = validateAdd(node); if (preflight != null) { throw new IllegalArgumentException(preflight); } putUnsafe(node); return this; } /** * Get a node by its id * * @param nodeId id of the wanted node * @return wanted node if it exists. Otherwise <code>null</code> */ @Nullable public DiscoveryNode get(String nodeId) { return nodes.get(nodeId); } private void putUnsafe(DiscoveryNode node) { nodes.put(node.getId(), node); } public Builder remove(String nodeId) { nodes.remove(nodeId); return this; } public Builder remove(DiscoveryNode node) { if (node.equals(nodes.get(node.getId()))) { nodes.remove(node.getId()); } return this; } public Builder masterNodeId(String masterNodeId) { this.masterNodeId = masterNodeId; return this; } public Builder localNodeId(String localNodeId) { this.localNodeId = localNodeId; return this; } /** * Checks that a node can be safely added to this node collection. * * @return null if all is OK or an error message explaining why a node can not be added. * * Note: if this method returns a non-null value, calling {@link #add(DiscoveryNode)} will fail with an * exception */ private String validateAdd(DiscoveryNode node) { for (ObjectCursor<DiscoveryNode> cursor : nodes.values()) { final DiscoveryNode existingNode = cursor.value; if (node.getAddress().equals(existingNode.getAddress()) && node.getId().equals(existingNode.getId()) == false) { return "can't add node " + node + ", found existing node " + existingNode + " with same address"; } if (node.getId().equals(existingNode.getId()) && node.equals(existingNode) == false) { return "can't add node " + node + ", found existing node " + existingNode + " with the same id but is a different node instance"; } } return null; } public DiscoveryNodes build() { ImmutableOpenMap.Builder<String, DiscoveryNode> dataNodesBuilder = ImmutableOpenMap.builder(); ImmutableOpenMap.Builder<String, DiscoveryNode> masterNodesBuilder = ImmutableOpenMap.builder(); ImmutableOpenMap.Builder<String, DiscoveryNode> ingestNodesBuilder = ImmutableOpenMap.builder(); Version minNodeVersion = Version.CURRENT; Version maxNodeVersion = Version.CURRENT; Version minNonClientNodeVersion = Version.CURRENT; for (ObjectObjectCursor<String, DiscoveryNode> nodeEntry : nodes) { if (nodeEntry.value.isDataNode()) { dataNodesBuilder.put(nodeEntry.key, nodeEntry.value); minNonClientNodeVersion = Version.min(minNonClientNodeVersion, nodeEntry.value.getVersion()); } if (nodeEntry.value.isMasterNode()) { masterNodesBuilder.put(nodeEntry.key, nodeEntry.value); minNonClientNodeVersion = Version.min(minNonClientNodeVersion, nodeEntry.value.getVersion()); } if (nodeEntry.value.isIngestNode()) { ingestNodesBuilder.put(nodeEntry.key, nodeEntry.value); } minNodeVersion = Version.min(minNodeVersion, nodeEntry.value.getVersion()); maxNodeVersion = Version.max(maxNodeVersion, nodeEntry.value.getVersion()); } return new DiscoveryNodes( nodes.build(), dataNodesBuilder.build(), masterNodesBuilder.build(), ingestNodesBuilder.build(), masterNodeId, localNodeId, minNonClientNodeVersion, maxNodeVersion, minNodeVersion ); } public boolean isLocalNodeElectedMaster() { return masterNodeId != null && masterNodeId.equals(localNodeId); } } }