/*
* 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.ignite.spi.discovery.tcp.internal;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.tostring.GridToStringInclude;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.P1;
import org.apache.ignite.internal.util.typedef.PN;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgnitePredicate;
import org.apache.ignite.lang.IgniteProductVersion;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Convenient way to represent topology for {@link org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi}
*/
public class TcpDiscoveryNodesRing {
/** Visible nodes filter. */
public static final IgnitePredicate<TcpDiscoveryNode> VISIBLE_NODES = new P1<TcpDiscoveryNode>() {
@Override public boolean apply(TcpDiscoveryNode node) {
if (node.visible()) {
assert node.order() > 0 : "Invalid node order: " + node;
return true;
}
return false;
}
};
/** Client nodes filter. */
private static final PN CLIENT_NODES = new PN() {
@Override public boolean apply(ClusterNode node) {
return node.isClient();
}
};
/** Local node. */
private TcpDiscoveryNode locNode;
/** All nodes in topology. */
@GridToStringInclude
private NavigableSet<TcpDiscoveryNode> nodes = new TreeSet<>();
/** All started nodes. */
@GridToStringExclude
private Map<UUID, TcpDiscoveryNode> nodesMap = new HashMap<>();
/** Current topology version */
private long topVer;
/** */
private long nodeOrder;
/** */
private long maxInternalOrder;
/** Lock. */
@GridToStringExclude
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
/** */
private IgniteProductVersion minNodeVer;
/**
* @return Minimum node version.
*/
public IgniteProductVersion minimumNodeVersion() {
rwLock.readLock().lock();
try {
return minNodeVer;
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Sets local node.
*
* @param locNode Local node.
*/
public void localNode(TcpDiscoveryNode locNode) {
assert locNode != null;
rwLock.writeLock().lock();
try {
this.locNode = locNode;
clear();
maxInternalOrder = locNode.internalOrder();
}
finally {
rwLock.writeLock().unlock();
}
}
/**
* Gets all nodes in the topology.
*
* @return Collection of all nodes.
*/
public Collection<TcpDiscoveryNode> allNodes() {
return nodes();
}
/**
* Gets visible nodes in the topology.
*
* @return Collection of visible nodes.
*/
public Collection<TcpDiscoveryNode> visibleNodes() {
return nodes(VISIBLE_NODES);
}
/**
* Gets remote nodes.
*
* @return Collection of remote nodes in grid.
*/
public Collection<TcpDiscoveryNode> remoteNodes() {
return nodes(F.remoteNodes(locNode.id()));
}
/**
* Gets visible remote nodes in the topology.
*
* @return Collection of visible remote nodes.
*/
public Collection<TcpDiscoveryNode> visibleRemoteNodes() {
return nodes(VISIBLE_NODES, F.remoteNodes(locNode.id()));
}
/**
* @return Client nodes.
*/
public Collection<TcpDiscoveryNode> clientNodes() {
return nodes(CLIENT_NODES);
}
/**
* Checks whether the topology has remote nodes in.
*
* @return {@code true} if the topology has remote nodes in.
*/
public boolean hasRemoteNodes() {
rwLock.readLock().lock();
try {
return nodes.size() > 1;
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Checks whether the topology has remote server nodes in.
*
* @return {@code true} if the topology has remote server nodes in.
*/
public boolean hasRemoteServerNodes() {
rwLock.readLock().lock();
try {
if (nodes.size() < 2)
return false;
for (TcpDiscoveryNode node : nodes)
if (!node.isClient() && !node.id().equals(locNode.id()))
return true;
return false;
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Adds node to topology, also initializes node last update time with current
* system time.
*
* @param node Node to add.
* @return {@code true} if such node was added and did not present previously in the topology.
*/
public boolean add(TcpDiscoveryNode node) {
assert node != null;
assert node.internalOrder() > 0;
rwLock.writeLock().lock();
try {
if (nodesMap.containsKey(node.id()))
return false;
long maxInternalOrder0 = maxInternalOrder();
assert node.internalOrder() > maxInternalOrder0 : "Adding node to the middle of the ring " +
"[ring=" + this + ", node=" + node + ']';
nodesMap.put(node.id(), node);
nodes = new TreeSet<>(nodes);
node.lastUpdateTime(U.currentTimeMillis());
nodes.add(node);
nodeOrder = node.internalOrder();
maxInternalOrder = node.internalOrder();
initializeMinimumVersion();
}
finally {
rwLock.writeLock().unlock();
}
return true;
}
/**
* @return Max internal order.
*/
public long maxInternalOrder() {
rwLock.readLock().lock();
try {
if (maxInternalOrder == 0) {
TcpDiscoveryNode last = nodes.last();
return last != null ? maxInternalOrder = last.internalOrder() : -1;
}
return maxInternalOrder;
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Restores topology from parameters values.
* <p>
* This method is called when new node receives topology from coordinator.
* In this case all nodes received are remote for local.
* <p>
* Also initializes nodes last update time with current system time.
*
* @param nodes List of remote nodes.
* @param topVer Topology version.
*/
public void restoreTopology(Iterable<TcpDiscoveryNode> nodes, long topVer) {
assert !F.isEmpty(nodes);
assert topVer > 0;
rwLock.writeLock().lock();
try {
locNode.internalOrder(topVer);
clear();
boolean firstAdd = true;
for (TcpDiscoveryNode node : nodes) {
if (nodesMap.containsKey(node.id()))
continue;
nodesMap.put(node.id(), node);
if (firstAdd) {
this.nodes = new TreeSet<>(this.nodes);
firstAdd = false;
}
node.lastUpdateTime(U.currentTimeMillis());
this.nodes.add(node);
}
nodeOrder = topVer;
initializeMinimumVersion();
}
finally {
rwLock.writeLock().unlock();
}
}
/**
* Finds node by ID.
*
* @param nodeId Node id to find.
* @return Node with ID provided or {@code null} if not found.
*/
@Nullable public TcpDiscoveryNode node(UUID nodeId) {
assert nodeId != null;
rwLock.readLock().lock();
try {
return nodesMap.get(nodeId);
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Removes node from the topology.
*
* @param nodeId ID of the node to remove.
* @return {@code true} if node was removed.
*/
@Nullable public TcpDiscoveryNode removeNode(UUID nodeId) {
assert nodeId != null;
assert !locNode.id().equals(nodeId);
rwLock.writeLock().lock();
try {
TcpDiscoveryNode rmv = nodesMap.remove(nodeId);
if (rmv != null) {
nodes = new TreeSet<>(nodes);
nodes.remove(rmv);
}
initializeMinimumVersion();
return rmv;
}
finally {
rwLock.writeLock().unlock();
}
}
/**
* Removes all remote nodes, leaves only local node.
* <p>
* This should be called when SPI should be disconnected from topology and
* reconnected back after.
*/
public void clear() {
rwLock.writeLock().lock();
try {
nodes = new TreeSet<>();
if (locNode != null)
nodes.add(locNode);
nodesMap = new HashMap<>();
if (locNode != null)
nodesMap.put(locNode.id(), locNode);
nodeOrder = 0;
maxInternalOrder = 0;
topVer = 0;
if (locNode != null)
minNodeVer = locNode.version();
}
finally {
rwLock.writeLock().unlock();
}
}
/**
* Finds coordinator in the topology.
*
* @return Coordinator node that gives versions to topology (node with the smallest order).
*/
@Nullable public TcpDiscoveryNode coordinator() {
rwLock.readLock().lock();
try {
if (F.isEmpty(nodes))
return null;
return coordinator(null);
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Finds coordinator in the topology filtering excluded nodes from the search.
* <p>
* This may be used when handling current coordinator leave or failure.
*
* @param excluded Nodes to exclude from the search (optional).
* @return Coordinator node among remaining nodes or {@code null} if all nodes are excluded.
*/
@Nullable public TcpDiscoveryNode coordinator(@Nullable Collection<TcpDiscoveryNode> excluded) {
rwLock.readLock().lock();
try {
Collection<TcpDiscoveryNode> filtered = serverNodes(excluded);
if (F.isEmpty(filtered))
return null;
return Collections.min(filtered);
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Finds next node in the topology.
*
* @return Next node.
*/
@Nullable public TcpDiscoveryNode nextNode() {
rwLock.readLock().lock();
try {
if (nodes.size() < 2)
return null;
return nextNode(null);
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Finds next node in the topology filtering excluded nodes from search.
* <p>
* This may be used when detecting and handling nodes failure.
*
* @param excluded Nodes to exclude from the search (optional). If provided,
* cannot contain local node.
* @return Next node or {@code null} if all nodes were filtered out or
* topology contains less than two nodes.
*/
@Nullable public TcpDiscoveryNode nextNode(@Nullable Collection<TcpDiscoveryNode> excluded) {
assert locNode.internalOrder() > 0 : locNode;
assert excluded == null || excluded.isEmpty() || !excluded.contains(locNode) : excluded;
rwLock.readLock().lock();
try {
Collection<TcpDiscoveryNode> filtered = serverNodes(excluded);
if (filtered.size() < 2)
return null;
Iterator<TcpDiscoveryNode> iter = filtered.iterator();
while (iter.hasNext()) {
TcpDiscoveryNode node = iter.next();
if (locNode.equals(node))
break;
}
return iter.hasNext() ? iter.next() : F.first(filtered);
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Gets current topology version.
*
* @return Current topology version.
*/
public long topologyVersion() {
rwLock.readLock().lock();
try {
return topVer;
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Sets new topology version.
*
* @param topVer New topology version (should be greater than current, otherwise no-op).
* @return {@code True} if topology has been changed.
*/
public boolean topologyVersion(long topVer) {
rwLock.writeLock().lock();
try {
if (this.topVer < topVer) {
this.topVer = topVer;
return true;
}
return false;
}
finally {
rwLock.writeLock().unlock();
}
}
/**
* Increments topology version and gets new value.
*
* @return Topology version (incremented).
*/
public long incrementTopologyVersion() {
rwLock.writeLock().lock();
try {
return ++topVer;
}
finally {
rwLock.writeLock().unlock();
}
}
/**
* Increments topology version and gets new value.
*
* @return Topology version (incremented).
*/
public long nextNodeOrder() {
rwLock.writeLock().lock();
try {
if (nodeOrder == 0)
nodeOrder = maxInternalOrder();
return ++nodeOrder;
}
finally {
rwLock.writeLock().unlock();
}
}
/**
* @param p Filters.
* @return Unmodifiable collection of nodes.
*/
private Collection<TcpDiscoveryNode> nodes(IgnitePredicate<? super TcpDiscoveryNode>... p) {
rwLock.readLock().lock();
try {
List<TcpDiscoveryNode> list = U.arrayList(nodes, p);
return Collections.unmodifiableCollection(list);
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Gets server nodes from topology.
*
* @param excluded Nodes to exclude from the search (optional).
* @return Collection of server nodes.
*/
private Collection<TcpDiscoveryNode> serverNodes(@Nullable final Collection<TcpDiscoveryNode> excluded) {
final boolean excludedEmpty = F.isEmpty(excluded);
return F.view(nodes, new P1<TcpDiscoveryNode>() {
@Override public boolean apply(TcpDiscoveryNode node) {
return !node.isClient() && (excludedEmpty || !excluded.contains(node));
}
});
}
/**
*
*/
private void initializeMinimumVersion() {
minNodeVer = null;
for (TcpDiscoveryNode node : nodes) {
if (minNodeVer == null || node.version().compareTo(minNodeVer) < 0)
minNodeVer = node.version();
}
}
/** {@inheritDoc} */
@Override public String toString() {
rwLock.readLock().lock();
try {
return S.toString(TcpDiscoveryNodesRing.class, this);
}
finally {
rwLock.readLock().unlock();
}
}
}