/*
* INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa
* Copyright 2013 INESC-ID and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 3.0 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.dataplacement;
import org.infinispan.commons.hash.Hash;
import org.infinispan.commons.hash.MurmurHash3;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.distribution.TestAddress;
import org.infinispan.distribution.ch.DefaultConsistentHash;
import org.infinispan.remoting.transport.Address;
import org.infinispan.stats.topK.StreamLibContainer;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.testng.annotations.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import static org.infinispan.dataplacement.AccessesManager.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test the functionality of the Remote Accesses Manager and the Object placement manager
*
* @author Pedro Ruivo
* @since 5.2
*/
@Test(groups = "functional", testName = "dataplacement.AccessesAndPlacementTest")
public class AccessesAndPlacementTest {
private static final Hash HASH = new MurmurHash3();
private final AtomicInteger KEY_NEXT_ID = new AtomicInteger(0);
public void testNoMovement() {
ClusterSnapshot clusterSnapshot = createClusterSnapshot(4);
ObjectPlacementManager manager = createObjectPlacementManager();
manager.resetState(clusterSnapshot);
Map<?, ?> newOwners = manager.calculateObjectsToMove();
assert newOwners.isEmpty();
}
@SuppressWarnings("AssertWithSideEffects")
public void testReturnValue() {
ClusterSnapshot clusterSnapshot = createClusterSnapshot(2);
ObjectPlacementManager manager = createObjectPlacementManager();
manager.resetState(clusterSnapshot);
assert !manager.aggregateRequest(clusterSnapshot.get(0), new ObjectRequest(null, null));
assert manager.aggregateRequest(clusterSnapshot.get(1), new ObjectRequest(null, null));
assert !manager.aggregateRequest(clusterSnapshot.get(0), new ObjectRequest(null, null));
assert !manager.aggregateRequest(clusterSnapshot.get(1), new ObjectRequest(null, null));
}
public void testObjectPlacement() {
ClusterSnapshot clusterSnapshot = createClusterSnapshot(4);
ObjectPlacementManager manager = createObjectPlacementManager();
manager.resetState(clusterSnapshot);
Map<Object, Long> request = new HashMap<Object, Long>();
TestKey key1 = new TestKey(1, clusterSnapshot.get(0), clusterSnapshot.get(1));
TestKey key2 = new TestKey(2, clusterSnapshot.get(2), clusterSnapshot.get(3));
request.put(key1, 1L);
manager.aggregateRequest(clusterSnapshot.get(2), new ObjectRequest(request, null));
request = new HashMap<Object, Long>();
request.put(key1, 1L);
manager.aggregateRequest(clusterSnapshot.get(3), new ObjectRequest(request, null));
request = new HashMap<Object, Long>();
request.put(key2, 1L);
manager.aggregateRequest(clusterSnapshot.get(0), new ObjectRequest(request, null));
request = new HashMap<Object, Long>();
request.put(key2, 1L);
manager.aggregateRequest(clusterSnapshot.get(1), new ObjectRequest(request, null));
Map<Object, OwnersInfo> newOwners = manager.calculateObjectsToMove();
assert newOwners.size() == 2;
assertOwner(newOwners.get(key1), 2, 3);
assertOwner(newOwners.get(key2), 0, 1);
}
public void testObjectPlacement2() {
ClusterSnapshot clusterSnapshot = createClusterSnapshot(4);
ObjectPlacementManager manager = createObjectPlacementManager();
manager.resetState(clusterSnapshot);
Map<Object, Long> remote = new HashMap<Object, Long>();
Map<Object, Long> local = new HashMap<Object, Long>();
TestKey key1 = new TestKey(1, clusterSnapshot.get(0), clusterSnapshot.get(1));
TestKey key2 = new TestKey(2, clusterSnapshot.get(2), clusterSnapshot.get(3));
remote.put(key2, 1L);
local.put(key1, 4L);
manager.aggregateRequest(clusterSnapshot.get(0), new ObjectRequest(remote, local));
remote = new HashMap<Object, Long>();
local = new HashMap<Object, Long>();
remote.put(key2, 3L);
local.put(key1, 1L);
manager.aggregateRequest(clusterSnapshot.get(1), new ObjectRequest(remote, local));
remote = new HashMap<Object, Long>();
local = new HashMap<Object, Long>();
remote.put(key1, 2L);
local.put(key2, 4L);
manager.aggregateRequest(clusterSnapshot.get(2), new ObjectRequest(remote, local));
remote = new HashMap<Object, Long>();
local = new HashMap<Object, Long>();
remote.put(key1, 3L);
local.put(key2, 2L);
manager.aggregateRequest(clusterSnapshot.get(3), new ObjectRequest(remote, null));
Map<Object, OwnersInfo> newOwners = manager.calculateObjectsToMove();
assert newOwners.size() == 2;
assertOwner(newOwners.get(key1), 0, 3);
assertOwner(newOwners.get(key2), 1, 2);
}
public void testObjectPlacement3() {
ClusterSnapshot clusterSnapshot = createClusterSnapshot(4);
ObjectPlacementManager manager = createObjectPlacementManager();
manager.resetState(clusterSnapshot);
Map<Object, Long> request = new HashMap<Object, Long>();
TestKey key = new TestKey(2, clusterSnapshot.get(2), clusterSnapshot.get(3));
request.put(key, 2L);
manager.aggregateRequest(clusterSnapshot.get(0), new ObjectRequest(request, null));
request = new HashMap<Object, Long>();
request.put(key, 3L);
manager.aggregateRequest(clusterSnapshot.get(1), new ObjectRequest(request, null));
request = new HashMap<Object, Long>();
request.put(key, 5L);
manager.aggregateRequest(clusterSnapshot.get(2), new ObjectRequest(null, request));
request = new HashMap<Object, Long>();
request.put(key, 6L);
manager.aggregateRequest(clusterSnapshot.get(3), new ObjectRequest(null, request));
Map<Object, OwnersInfo> newOwners = manager.calculateObjectsToMove();
assert newOwners.isEmpty();
}
public void testRemoteAccesses() {
ClusterSnapshot clusterSnapshot = createClusterSnapshot(4);
AccessesManager manager = createRemoteAccessManager();
manager.resetState(clusterSnapshot);
StreamLibContainer container = StreamLibContainer.getInstance();
container.setActive(true);
container.setCapacity(2);
container.resetAll();
TestKey key1 = new TestKey(1, clusterSnapshot.get(0), clusterSnapshot.get(1));
TestKey key2 = new TestKey(2, clusterSnapshot.get(1), clusterSnapshot.get(2));
TestKey key3 = new TestKey(3, clusterSnapshot.get(2), clusterSnapshot.get(3));
TestKey key4 = new TestKey(4, clusterSnapshot.get(3), clusterSnapshot.get(0));
addKey(key1, false, 10, container);
addKey(key2, true, 5, container);
addKey(key3, true, 15, container);
addKey(key4, false, 2, container);
manager.calculateAccesses();
Map<Object, Long> remote = new HashMap<Object, Long>();
Map<Object, Long> local = new HashMap<Object, Long>();
local.put(key1, 10L);
assertAccesses(manager.getObjectRequestForAddress(clusterSnapshot.get(0)), remote, local);
remote.clear();
local.clear();
remote.put(key2, 5L);
assertAccesses(manager.getObjectRequestForAddress(clusterSnapshot.get(1)), remote, local);
remote.clear();
local.clear();
remote.put(key3, 15L);
assertAccesses(manager.getObjectRequestForAddress(clusterSnapshot.get(2)), remote, local);
remote.clear();
local.clear();
local.put(key4, 2L);
assertAccesses(manager.getObjectRequestForAddress(clusterSnapshot.get(3)), remote, local);
}
public void testRemoteTopKeyRequest() {
RemoteTopKeyRequest request = new RemoteTopKeyRequest(10);
TestKey key1 = createRandomKey();
TestKey key2 = createRandomKey();
TestKey key3 = createRandomKey();
TestKey key4 = createRandomKey();
Map<Object, Long> counter = new HashMap<Object, Long>();
counter.put(key1, 1L);
counter.put(key2, 1L);
counter.put(key3, 1L);
counter.put(key4, 1L);
request.merge(counter, 1);
assertKeyAccess(key1, request, 1);
assertKeyAccess(key2, request, 1);
assertKeyAccess(key3, request, 1);
assertKeyAccess(key4, request, 1);
counter.put(key1, 1L);
counter.put(key2, 2L);
counter.put(key3, 3L);
counter.put(key4, 4L);
request.merge(counter, 2);
assertKeyAccess(key1, request, 3);
assertKeyAccess(key2, request, 5);
assertKeyAccess(key3, request, 7);
assertKeyAccess(key4, request, 9);
assertSortedKeyAccess(key1, request, 3, 3);
assertSortedKeyAccess(key2, request, 5, 2);
assertSortedKeyAccess(key3, request, 7, 1);
assertSortedKeyAccess(key4, request, 9, 0);
assertSortedKeyAccess(request);
counter.clear();
TestKey[] keys = new TestKey[11];
for (int i = 0; i < keys.length; ++i) {
TestKey key = createRandomKey();
keys[i] = key;
counter.put(key, (long) i);
}
request.merge(counter, 1);
assertKeyAccess(key1, request, 3);
assertKeyAccess(key2, request, 5);
assertKeyAccess(key3, request, 7);
assertKeyAccess(key4, request, 9);
for (int i = 1; i < keys.length; ++i) {
assertKeyAccess(keys[i], request, i);
}
int idx = 0;
assertSortedKeyAccess(keys[10], request, 10, idx++);
assertSortedKeyAccess(keys[9], request, 9, idx++);
assertSortedKeyAccess(key4, request, 9, idx++);
assertSortedKeyAccess(keys[8], request, 8, idx++);
assertSortedKeyAccess(keys[7], request, 7, idx++);
assertSortedKeyAccess(key3, request, 7, idx++);
assertSortedKeyAccess(keys[6], request, 6, idx++);
assertSortedKeyAccess(keys[5], request, 5, idx++);
assertSortedKeyAccess(key2, request, 5, idx++);
assertSortedKeyAccess(keys[4], request, 4, idx++);
assertSortedKeyAccess(keys[3], request, 3, idx++);
assertSortedKeyAccess(key1, request, 3, idx++);
assertSortedKeyAccess(keys[2], request, 2, idx++);
assertSortedKeyAccess(keys[1], request, 1, idx);
assertSortedKeyAccess(request);
assertNoKeyAccess(keys[0], request);
}
public void testLocalTopKeyRequest() {
LocalTopKeyRequest request = new LocalTopKeyRequest();
TestKey key1 = createRandomKey();
TestKey key2 = createRandomKey();
TestKey key3 = createRandomKey();
TestKey key4 = createRandomKey();
Map<Object, Long> counter = new HashMap<Object, Long>();
counter.put(key1, 1L);
counter.put(key2, 1L);
counter.put(key3, 1L);
counter.put(key4, 1L);
request.merge(counter, 1);
assertKeyAccess(key1, request, 1);
assertKeyAccess(key2, request, 1);
assertKeyAccess(key3, request, 1);
assertKeyAccess(key4, request, 1);
counter.put(key1, 1L);
counter.put(key2, 2L);
counter.put(key3, 3L);
counter.put(key4, 4L);
request.merge(counter, 2);
assertKeyAccess(key1, request, 3);
assertKeyAccess(key2, request, 5);
assertKeyAccess(key3, request, 7);
assertKeyAccess(key4, request, 9);
counter.clear();
TestKey[] keys = new TestKey[11];
for (int i = 0; i < keys.length; ++i) {
TestKey key = createRandomKey();
keys[i] = key;
counter.put(key, (long) i);
}
request.merge(counter, 1);
assertKeyAccess(key1, request, 3);
assertKeyAccess(key2, request, 5);
assertKeyAccess(key3, request, 7);
assertKeyAccess(key4, request, 9);
for (int i = 1; i < keys.length; ++i) {
assertKeyAccess(keys[i], request, i);
}
assertNoKeyAccess(keys[0], request);
}
private void assertNoKeyAccess(Object key, RemoteTopKeyRequest request) {
assert !request.contains(key) : "Key " + key + " has found in map";
for (KeyAccess keyAccess : request.getSortedKeyAccess()) {
assert !keyAccess.getKey().equals(key) : "Key " + key + " has found in list";
}
}
private void assertNoKeyAccess(Object key, LocalTopKeyRequest request) {
assert !request.contains(key) : "Key " + key + " has found";
}
private void assertKeyAccess(Object key, RemoteTopKeyRequest request, long accesses) {
assert request.contains(key) : "Key " + key + " not found";
KeyAccess keyAccess = request.get(key);
assert keyAccess.getAccesses() == accesses : "Number of accesses: " + keyAccess.getAccesses() + " != " + accesses;
assert keyAccess.getKey().equals(key) : "Key is different: " + keyAccess.getKey() + " != " + key;
}
private void assertKeyAccess(Object key, LocalTopKeyRequest request, long accesses) {
assert request.contains(key) : "Key " + key + " not found";
KeyAccess keyAccess = request.get(key);
assert keyAccess.getAccesses() == accesses : "Number of accesses: " + keyAccess.getAccesses() + " != " + accesses;
assert keyAccess.getKey().equals(key) : "Key is different: " + keyAccess.getKey() + " != " + key;
}
private void assertSortedKeyAccess(Object key, RemoteTopKeyRequest request, long accesses, int pos) {
List<KeyAccess> keyAccessList = request.getSortedKeyAccess();
assert keyAccessList.size() > pos : "Size (" + keyAccessList.size() + ") is smaller than " + pos;
assert keyAccessList.get(pos).getKey().equals(key) : "Key is different [" + pos + "]: " +
keyAccessList.get(pos).getKey() + " != " + key;
assert keyAccessList.get(pos).getAccesses() == accesses : "Number of accesses [" + pos + "]: " +
keyAccessList.get(pos).getAccesses() + " != " + accesses;
}
private void assertSortedKeyAccess(RemoteTopKeyRequest request) {
List<KeyAccess> keyAccessList = request.getSortedKeyAccess();
if (keyAccessList.isEmpty()) {
return;
}
long maxValue = keyAccessList.get(0).getAccesses();
for (KeyAccess keyAccess : keyAccessList) {
assert keyAccess.getAccesses() <= maxValue : "Different order: " + keyAccess.getAccesses() + " > " + maxValue;
maxValue = keyAccess.getAccesses();
}
}
private TestKey createRandomKey() {
return new TestKey(KEY_NEXT_ID.incrementAndGet(), new TestAddress(0), new TestAddress(1));
}
private void assertAccesses(ObjectRequest request, Map<Object, Long> remote, Map<Object, Long> local) {
Map<Object, Long> remoteAccesses = request.getRemoteAccesses();
Map<Object, Long> localAccesses = request.getLocalAccesses();
assert remoteAccesses.size() == remote.size();
assert localAccesses.size() == local.size();
for (Map.Entry<Object, Long> entry: remote.entrySet()) {
long value1 = entry.getValue();
long value2 = remoteAccesses.get(entry.getKey());
assert value1 == value2;
}
for (Map.Entry<Object, Long> entry: local.entrySet()) {
long value1 = entry.getValue();
long value2 = localAccesses.get(entry.getKey());
assert value1 == value2;
}
}
private void addKey(Object key, boolean remote, int count, StreamLibContainer container) {
for (int i = 0; i < count; ++i) {
container.addGet(key, remote);
}
}
private void assertOwner(OwnersInfo ownersInfo, Integer... newOwners) {
assert ownersInfo != null;
List<Integer> expectedOwners = Arrays.asList(newOwners);
List<Integer> owners = ownersInfo.getNewOwnersIndexes();
assert expectedOwners.size() == owners.size();
assert expectedOwners.containsAll(owners);
}
private ClusterSnapshot createClusterSnapshot(int size) {
List<Address> members = new ArrayList<Address>(size);
for (int i = 0; i < size; ++i) {
members.add(new TestAddress(i));
}
return new ClusterSnapshot(members.toArray(new Address[size]), HASH);
}
private ObjectPlacementManager createObjectPlacementManager() {
return new ObjectPlacementManager(getMockDistributionManager(), new MurmurHash3(), 2);
}
private AccessesManager createRemoteAccessManager() {
return new AccessesManager(getMockDistributionManager(), 1000);
}
private DistributionManager getMockDistributionManager() {
DefaultConsistentHash consistentHash = mock(DefaultConsistentHash.class);
when(consistentHash.locate(isA(TestKey.class), anyInt())).thenAnswer(new Answer<List<Address>>() {
@Override
public List<Address> answer(InvocationOnMock invocationOnMock) throws Throwable {
return new LinkedList<Address>(((TestKey) invocationOnMock.getArguments()[0]).getOwners());
}
});
when(consistentHash.locateAll(anyCollectionOf(Object.class), anyInt())).thenAnswer(new Answer<Object>() {
@SuppressWarnings("unchecked")
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
Collection<Object> keys = (Collection<Object>) invocationOnMock.getArguments()[0];
Map<Object, List<Address>> addresses = new HashMap<Object, List<Address>>();
for (Object key : keys) {
if (key instanceof TestKey) {
addresses.put(key, new LinkedList<Address>(((TestKey) key).getOwners()));
}
}
return addresses;
}
});
DistributionManager distributionManager = mock(DistributionManager.class);
when(distributionManager.locate(isA(TestKey.class))).thenAnswer(new Answer<List<Address>>() {
@Override
public List<Address> answer(InvocationOnMock invocationOnMock) throws Throwable {
return new LinkedList<Address>(((TestKey) invocationOnMock.getArguments()[0]).getOwners());
}
});
when(distributionManager.getConsistentHash()).thenReturn(consistentHash);
return distributionManager;
}
private class TestKey {
private final Collection<Address> owners;
private final int id;
private TestKey(int id, Address... owners) {
this.id = id;
this.owners = Arrays.asList(owners);
}
public Collection<Address> getOwners() {
return owners;
}
public int getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestKey testKey = (TestKey) o;
return id == testKey.id;
}
@Override
public int hashCode() {
return id;
}
@Override
public String toString() {
return "TestKey{" +
"id=" + id +
'}';
}
}
}