/*
* 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.internal.client.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
/**
* Test for consistent hash management class.
*/
public class ClientConsistentHashSelfTest extends GridCommonAbstractTest {
/** Replicas count. */
private static final int REPLICAS = 512;
/**
* Test hash codes collisions.
*
* @throws Exception In case of any exception.
*/
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() < 10) {
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.
GridClientConsistentHash<UUID> hash = new GridClientConsistentHash<>();
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.
*/
public void testTreeSetRestrictions() throws Exception {
// Constructs hash without explicit node's comparator.
GridClientConsistentHash<Object> hash = new GridClientConsistentHash<>();
try {
// Add several objects with the same hash without neither natural ordering nor comparator.
hash.addNode(new Object() { public int hashCode() { return 0; } }, 1);
hash.addNode(new Object() { 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 GridClientConsistentHash<>(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() { public int hashCode() { return 0; } }, 1);
hash.addNode(new Object() { public int hashCode() { return 0; } }, 1);
info("Expected pass due to internal TreeSet has explicit comparator.");
}
/**
* Validate generated hashes.<p>
* Note! This test should be ported into all supported platforms.
*/
public void testHashGeneraton() {
// Validate strings.
checkHash("", -1484017934);
checkHash("1", -80388575);
checkHash("a", -873690096);
checkHash("Hadoop\u3092\u6bba\u3059", -695300527);
checkHash("key1", -2067461682);
// Validate primitives.
checkHash(true, 1669973725);
checkHash(false, -1900934144);
checkHash(3, 386050343);
checkHash(1000000000, -547312286);
checkHash(0x7fffffff, 473949739);
checkHash(0xffffffff, -1399925094);
checkHash(0x7fffffffffffffffL, 201097861);
checkHash(0xffffffffffffffffL, -1484017934);
checkHash(1.4e-45f, 1262722378);
checkHash(3.4028235e+38f, 1313755354);
checkHash(4.9e-324, 1262722378);
checkHash(1.7976931348623157e+308, -783615357);
// Validate objects.
checkHash(UUID.fromString("4d180911-21c9-48f2-a1e6-7bc1daf588a0"), -440525148);
checkUUID("224ea4cd-f449-4dcb-869a-5317c63bd619", 806670090);
checkUUID("fdc9ec54-ff53-4fdb-8239-5a3ac1fb31bd", -354375826);
checkUUID("0f9c9b94-02ae-45a6-9d5c-a066dbdf2636", -1312538272);
checkUUID("d8f1f916-4357-4cfe-a7df-49d4721690bf", -482944041);
checkUUID("d67eb652-4e76-47fb-ad4e-cd902d9b868a", -449444069);
checkUUID("c77ffeae-78a1-4ee6-a0fd-8d197a794412", -168980875);
checkUUID("35de9f21-3c9b-4f4a-a7d5-3e2c6cb01564", -383915637);
}
/**
* Test mapping to nodes.
*/
@SuppressWarnings("UnaryPlus")
public void testMappingToNodes() {
String n1 = "node #1";
String n2 = "node #2";
String n3 = "node #3";
String n4 = "node #4";
List<String> nodes = Arrays.asList(n1, n2, n3, n4);
GridClientConsistentHash<String> hash = new GridClientConsistentHash<>();
for (String node : nodes)
hash.addNode(node, 5);
Map<Object, String> data = new LinkedHashMap<>();
data.put("", n1);
data.put("asdf", n3);
data.put("224ea4cd-f449-4dcb-869a-5317c63bd619", n2);
data.put("fdc9ec54-ff53-4fdb-8239-5a3ac1fb31bd", n4);
data.put("0f9c9b94-02ae-45a6-9d5c-a066dbdf2636", n1);
data.put("d8f1f916-4357-4cfe-a7df-49d4721690bf", n3);
data.put("c77ffeae-78a1-4ee6-a0fd-8d197a794412", n4);
data.put("35de9f21-3c9b-4f4a-a7d5-3e2c6cb01564", n4);
data.put("d67eb652-4e76-47fb-ad4e-cd902d9b868a", n4);
data.put(0, n1);
data.put(1, n4);
data.put(12, n3);
data.put(123, n3);
data.put(1234, n3);
data.put(12345, n4);
data.put(123456, n3);
data.put(1234567, n4);
data.put(12345678, n4);
data.put(123456789, n4);
data.put(1234567890, n3);
data.put(1234567890L, n3);
data.put(12345678901L, n4);
data.put(123456789012L, n2);
data.put(1234567890123L, n4);
data.put(12345678901234L, n1);
data.put(123456789012345L, n1);
data.put(1234567890123456L, n3);
data.put(-23456789012345L, n2);
data.put(-2345678901234L, n1);
data.put(-234567890123L, n4);
data.put(-23456789012L, n3);
data.put(-2345678901L, n3);
data.put(-234567890L, n1);
data.put(-234567890, n4);
data.put(-23456789, n4);
data.put(-2345678, n4);
data.put(-234567, n4);
data.put(-23456, n4);
data.put(-2345, n1);
data.put(-234, n4);
data.put(-23, n3);
data.put(-2, n4);
data.put(0x80000000, n2);
data.put(0x7fffffff, n4);
data.put(0x8000000000000000L, n2);
data.put(0x7fffffffffffffffL, n2);
data.put(+1.1, n1);
data.put(-10.01, n3);
data.put(+100.001, n3);
data.put(-1000.0001, n4);
data.put(+1.7976931348623157E+308, n4);
data.put(-1.7976931348623157E+308, n4);
data.put(+4.9E-324, n4);
data.put(-4.9E-324, n3);
for (Map.Entry<Object, String> entry : data.entrySet())
assertEquals("Validate key '" + entry.getKey() + "'.", entry.getValue(), hash.node(entry.getKey()));
for (Map.Entry<Object, String> entry : data.entrySet())
assertEquals("Validate key '" + entry.getKey() + "'.", entry.getValue(), hash.node(entry.getKey(), nodes));
//
// Change order nodes were added.
//
nodes = new ArrayList<>(nodes);
Collections.reverse(nodes);
// Reset consistent hash with new nodes order.
hash = new GridClientConsistentHash<>();
for (String node : nodes)
hash.addNode(node, 5);
for (Map.Entry<Object, String> entry : data.entrySet())
assertEquals("Validate key '" + entry.getKey() + "'.", entry.getValue(), hash.node(entry.getKey()));
for (Map.Entry<Object, String> entry : data.entrySet())
assertEquals("Validate key '" + entry.getKey() + "'.", entry.getValue(), hash.node(entry.getKey(), nodes));
}
/**
* Check unique id and generated hash code.
*
* @param uuid String presentation of unique id.
* @param code Expected hash code.
*/
private void checkUUID(String uuid, int code) {
checkHash(UUID.fromString(uuid), code);
}
/**
* Check hash generation for the specified object.
*
* @param o Object to verify hash generation for.
* @param code Expected hash code.
*/
private void checkHash(Object o, int code) {
int i = GridClientConsistentHash.hash(o);
assertEquals("Check affinity for object: " + o, code, i);
}
}