| /* |
| * 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.lang.utils; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.UUID; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import org.apache.ignite.internal.util.GridConsistentHash; |
| import org.apache.ignite.internal.util.typedef.F; |
| import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; |
| import org.apache.ignite.testframework.junits.common.GridCommonTest; |
| import org.junit.Test; |
| |
| /** |
| * Consistent hash test. |
| */ |
| @GridCommonTest(group = "Lang") |
| public class GridConsistentHashSelfTest extends GridCommonAbstractTest { |
| /** */ |
| private static final int NODES = 20; |
| |
| /** */ |
| private static final int REPLICAS = 512; |
| |
| /** |
| * Initializes hash. |
| * |
| * @param hash Hash to initialize. |
| * @param replicas Replicas. |
| * @param nodes Array of nodes. |
| * @return Passed in hash for chaining. |
| */ |
| private GridConsistentHash<UUID> initialize(GridConsistentHash<UUID> hash, int replicas, UUID... nodes) { |
| assert hash != null; |
| |
| int before = hash.count(); |
| |
| for (UUID node : nodes) { |
| int cnt = hash.count(); |
| |
| assert hash.addNode(node, replicas); |
| |
| assertEquals(cnt + 1, hash.count()); |
| } |
| |
| int after = hash.count(); |
| |
| assert before + nodes.length == after : "Invalid number of nodes [hashed=" + hash.nodes() + |
| ", added=" + Arrays.toString(nodes) + ']'; |
| |
| return hash; |
| } |
| |
| /** |
| * @param hash Hash to clean. |
| */ |
| private void clean(GridConsistentHash<UUID> hash) { |
| if (hash != null) { |
| int cnt = hash.count(); |
| |
| assert hash.removeNode(hash.random()); |
| |
| assertEquals(cnt - 1, hash.count()); |
| |
| // info("Cleaning nodes", hash.nodes()); |
| |
| hash.removeNodes(hash.nodes()); |
| |
| hash.clear(); |
| |
| assertEquals(0, hash.size()); |
| assertEquals("Invalid hash: " + hash.nodes(), 0, hash.count()); |
| |
| assert hash.isEmpty(); |
| } |
| } |
| |
| /** |
| * Test hash codes collisions. |
| * |
| * @throws Exception In case of any exception. |
| */ |
| @Test |
| public void testCollisions() throws Exception { |
| Map<Integer, Set<UUID>> map = new HashMap<>(); |
| |
| // Different nodes, but collide hash codes. |
| Collection<UUID> nodes = new LinkedHashSet<>(); |
| |
| // Generate several nodes with collide hash codes. |
| while (nodes.size() < 8) { |
| UUID uuid = UUID.randomUUID(); |
| int hashCode = uuid.hashCode(); |
| |
| Set<UUID> set = map.get(hashCode); |
| |
| if (set == null) |
| map.put(hashCode, set = new LinkedHashSet<>()); |
| |
| set.add(uuid); |
| |
| if (set.size() > 1) |
| nodes.addAll(set); |
| } |
| |
| map.clear(); // Clean up. |
| |
| GridConsistentHash<UUID> hash = new GridConsistentHash<>(); |
| |
| hash.addNodes(nodes, REPLICAS); |
| |
| boolean fail = false; |
| |
| for (UUID exp : nodes) { |
| UUID act = hash.node(0, Arrays.asList(exp)); |
| |
| if (exp.equals(act)) |
| info("Validation succeed [exp=" + exp + ", act=" + act + ']'); |
| else { |
| info("Validation failed [exp=" + exp + ", act=" + act + ']'); |
| |
| fail = true; |
| } |
| } |
| |
| if (fail) |
| fail("Failed to resolve consistent hash node, when node's hash codes collide: " + nodes); |
| } |
| |
| /** |
| * Test restrictions from internal {@link TreeSet} usage. |
| * |
| * @throws Exception In case of any exception. |
| */ |
| @Test |
| public void testTreeSetRestrictions() throws Exception { |
| // Constructs hash without explicit node's comparator. |
| GridConsistentHash<Object> hash = new GridConsistentHash<>(); |
| |
| try { |
| // Add several objects with the same hash without neigther natural ordering nor comparator. |
| hash.addNode(new Object() { |
| @Override public int hashCode() { |
| return 0; |
| } |
| }, 1); |
| hash.addNode(new Object() { |
| @Override public int hashCode() { |
| return 0; |
| } |
| }, 1); |
| |
| fail("Expects failed due to internal TreeSet requires comparator or natural ordering."); |
| } |
| catch (ClassCastException e) { |
| info("Expected fail due to internal TreeSet requires comparator or natural ordering: " + e.getMessage()); |
| } |
| |
| // Constructs hash with explicit node's comparator. |
| hash = new GridConsistentHash<>(new Comparator<Object>() { |
| @Override public int compare(Object o1, Object o2) { |
| // Such comparator is invalid for production code, but acceptable for current test purposes. |
| return System.identityHashCode(o1) - System.identityHashCode(o2); |
| } |
| }, null); |
| |
| // Add several objects with the same hash into consistent hash with explicit comparator. |
| hash.addNode(new Object() { |
| @Override public int hashCode() { |
| return 0; |
| } |
| }, 1); |
| hash.addNode(new Object() { |
| @Override public int hashCode() { |
| return 0; |
| } |
| }, 1); |
| |
| info("Expected pass due to internal TreeSet has explicit comparator."); |
| } |
| |
| /** |
| * |
| */ |
| @Test |
| public void testOneNode() { |
| GridConsistentHash<UUID> hash = new GridConsistentHash<>(); |
| |
| UUID nodeId = UUID.randomUUID(); |
| |
| initialize(hash, REPLICAS, nodeId); |
| |
| Collection<UUID> id = hash.nodes("a", 2, F.asList(nodeId)); |
| |
| assertFalse(F.isEmpty(id)); |
| assertEquals(1, id.size()); |
| assertEquals(nodeId, id.iterator().next()); |
| } |
| |
| /** |
| * |
| */ |
| @Test |
| public void testHistory() { |
| for (int i = NODES; i-- > 0; ) { |
| GridConsistentHash<UUID> hash = new GridConsistentHash<>(); |
| |
| UUID[] nodes = nodes(i + 1); |
| |
| initialize(hash, REPLICAS, nodes); |
| |
| Collection<UUID> selected = new HashSet<>(); |
| |
| for (int j = i + 1; j-- > 0;) { |
| String key = UUID.randomUUID().toString(); |
| |
| selected.add(hash.node(key)); |
| |
| hash.removeNode(nodes[j]); |
| } |
| |
| info("Number of history nodes for topology [history=" + selected.size() + |
| ", topology=" + (i + 1) + ", selected=" + selected + ']'); |
| } |
| } |
| |
| /** |
| * @param msg Message. |
| * @param c Collection. |
| */ |
| private void info(String msg, Collection c) { |
| info(msg + " [size=" + c.size() + ", col=" + c + ']'); |
| } |
| |
| /** |
| * @param nodes Nodes. |
| * @return Nodes. |
| */ |
| private UUID[] nodes(int nodes) { |
| UUID[] ids = new UUID[nodes]; |
| |
| for (int i = 0; i < nodes; i++) |
| ids[i] = UUID.randomUUID(); |
| |
| return ids; |
| } |
| |
| /** |
| * @param hash Hash. |
| * @param replicas Replicas. |
| * @param nodes Nodes. |
| * @return Runnable. |
| */ |
| private Runnable initializer(final GridConsistentHash<UUID> hash, final int replicas, final UUID[] nodes) { |
| return new Runnable() { |
| @Override public void run() { |
| initialize(hash, replicas, nodes); |
| } |
| }; |
| } |
| |
| /** |
| * @param hash Hash. |
| * @param keys Keys. |
| * @return Runnable. |
| */ |
| private Runnable hasher(final GridConsistentHash<UUID> hash, final String[] keys) { |
| return new Runnable() { |
| @Override public void run() { |
| for (String k : keys) { |
| assert hash.node(k) != null; |
| } |
| } |
| }; |
| } |
| |
| /** |
| * @param hash Hash. |
| * @param cnts Counts. |
| * @param mappings Mappings. |
| * @param keys Keys. |
| */ |
| private void hash(GridConsistentHash<UUID> hash, Map<UUID, AtomicInteger> cnts, Map<String, UUID> mappings, |
| String[] keys) { |
| for (String k : keys) { |
| UUID id = hash.node(k); |
| |
| assert id != null; |
| |
| AtomicInteger i = cnts.get(id); |
| |
| if (i == null) |
| cnts.put(id, i = new AtomicInteger()); |
| |
| i.incrementAndGet(); |
| |
| mappings.put(k, id); |
| } |
| } |
| |
| /** |
| * |
| * @param m1 Map 1. |
| * @param m2 Map 2. |
| * @param keys Keys. |
| * @return Reassignment count. |
| */ |
| private int compare(Map<String, UUID> m1, Map<String, UUID> m2, String[] keys) { |
| int cnt = 0; |
| |
| // Check reassignment percentages. |
| for (String key : keys) { |
| UUID id1 = m1.get(key); |
| UUID id2 = m2.get(key); |
| |
| assert id1 != null; |
| assert id2 != null; |
| |
| if (!id1.equals(id2)) |
| cnt++; |
| } |
| |
| return cnt; |
| } |
| |
| /** |
| * @param cnt Number of keys to create. |
| * @return Array of keys. |
| */ |
| private String[] keys(int cnt) { |
| String[] keys = new String[cnt]; |
| |
| for (int i = 0; i < cnt; i++) |
| keys[i] = UUID.randomUUID().toString(); |
| |
| return keys; |
| } |
| } |