/*
* 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.cassandra.dht;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.locator.AbstractNetworkTopologySnitch;
public class RangeFetchMapCalculatorTest
{
@BeforeClass
public static void setupUpSnitch()
{
DatabaseDescriptor.daemonInitialization();
DatabaseDescriptor.setEndpointSnitch(new AbstractNetworkTopologySnitch()
{
//Odd IPs are in DC1 and Even are in DC2. Endpoints upto .14 will have unique racks and
// then will be same for a set of three.
@Override
public String getRack(InetAddress endpoint)
{
return "RAC1";
}
@Override
public String getDatacenter(InetAddress endpoint)
{
if (getIPLastPart(endpoint) <= 50)
return DatabaseDescriptor.getLocalDataCenter();
else if (getIPLastPart(endpoint) % 2 == 0)
return DatabaseDescriptor.getLocalDataCenter();
else
return DatabaseDescriptor.getLocalDataCenter() + "Remote";
}
private int getIPLastPart(InetAddress endpoint)
{
String str = endpoint.toString();
int index = str.lastIndexOf(".");
return Integer.parseInt(str.substring(index + 1).trim());
}
});
}
@Test
public void testWithSingleSource() throws Exception
{
Multimap<Range<Token>, InetAddress> rangesWithSources = HashMultimap.create();
addRangeAndSources(rangesWithSources, 1, 10, "127.0.0.1");
addRangeAndSources(rangesWithSources, 11, 20, "127.0.0.2");
addRangeAndSources(rangesWithSources, 21, 30, "127.0.0.3");
addRangeAndSources(rangesWithSources, 31, 40, "127.0.0.4");
addRangeAndSources(rangesWithSources, 41, 50, "127.0.0.5");
RangeFetchMapCalculator calculator = new RangeFetchMapCalculator(rangesWithSources, new ArrayList<RangeStreamer.ISourceFilter>(), "Test");
Multimap<InetAddress, Range<Token>> map = calculator.getRangeFetchMap();
validateRange(rangesWithSources, map);
Assert.assertEquals(4, map.asMap().keySet().size());
}
@Test
public void testWithNonOverlappingSource() throws Exception
{
Multimap<Range<Token>, InetAddress> rangesWithSources = HashMultimap.create();
addRangeAndSources(rangesWithSources, 1, 10, "127.0.0.1", "127.0.0.2");
addRangeAndSources(rangesWithSources, 11, 20, "127.0.0.3", "127.0.0.4");
addRangeAndSources(rangesWithSources, 21, 30, "127.0.0.5", "127.0.0.6");
addRangeAndSources(rangesWithSources, 31, 40, "127.0.0.7", "127.0.0.8");
addRangeAndSources(rangesWithSources, 41, 50, "127.0.0.9", "127.0.0.10");
RangeFetchMapCalculator calculator = new RangeFetchMapCalculator(rangesWithSources, new ArrayList<RangeStreamer.ISourceFilter>(), "Test");
Multimap<InetAddress, Range<Token>> map = calculator.getRangeFetchMap();
validateRange(rangesWithSources, map);
Assert.assertEquals(5, map.asMap().keySet().size());
}
@Test
public void testWithRFThreeReplacement() throws Exception
{
Multimap<Range<Token>, InetAddress> rangesWithSources = HashMultimap.create();
addRangeAndSources(rangesWithSources, 1, 10, "127.0.0.1", "127.0.0.2");
addRangeAndSources(rangesWithSources, 11, 20, "127.0.0.2", "127.0.0.3");
addRangeAndSources(rangesWithSources, 21, 30, "127.0.0.3", "127.0.0.4");
RangeFetchMapCalculator calculator = new RangeFetchMapCalculator(rangesWithSources, new ArrayList<RangeStreamer.ISourceFilter>(), "Test");
Multimap<InetAddress, Range<Token>> map = calculator.getRangeFetchMap();
validateRange(rangesWithSources, map);
//We should validate that it streamed from 3 unique sources
Assert.assertEquals(3, map.asMap().keySet().size());
}
@Test
public void testForMultipleRoundsComputation() throws Exception
{
Multimap<Range<Token>, InetAddress> rangesWithSources = HashMultimap.create();
addRangeAndSources(rangesWithSources, 1, 10, "127.0.0.3");
addRangeAndSources(rangesWithSources, 11, 20, "127.0.0.3");
addRangeAndSources(rangesWithSources, 21, 30, "127.0.0.3");
addRangeAndSources(rangesWithSources, 31, 40, "127.0.0.3");
addRangeAndSources(rangesWithSources, 41, 50, "127.0.0.3", "127.0.0.2");
RangeFetchMapCalculator calculator = new RangeFetchMapCalculator(rangesWithSources, new ArrayList<RangeStreamer.ISourceFilter>(), "Test");
Multimap<InetAddress, Range<Token>> map = calculator.getRangeFetchMap();
validateRange(rangesWithSources, map);
//We should validate that it streamed from 2 unique sources
Assert.assertEquals(2, map.asMap().keySet().size());
assertArrays(Arrays.asList(generateRange(1, 10), generateRange(11, 20), generateRange(21, 30), generateRange(31, 40)),
map.asMap().get(InetAddress.getByName("127.0.0.3")));
assertArrays(Arrays.asList(generateRange(41, 50)), map.asMap().get(InetAddress.getByName("127.0.0.2")));
}
@Test
public void testForMultipleRoundsComputationWithLocalHost() throws Exception
{
Multimap<Range<Token>, InetAddress> rangesWithSources = HashMultimap.create();
addRangeAndSources(rangesWithSources, 1, 10, "127.0.0.1");
addRangeAndSources(rangesWithSources, 11, 20, "127.0.0.1");
addRangeAndSources(rangesWithSources, 21, 30, "127.0.0.1");
addRangeAndSources(rangesWithSources, 31, 40, "127.0.0.1");
addRangeAndSources(rangesWithSources, 41, 50, "127.0.0.1", "127.0.0.2");
RangeFetchMapCalculator calculator = new RangeFetchMapCalculator(rangesWithSources, new ArrayList<RangeStreamer.ISourceFilter>(), "Test");
Multimap<InetAddress, Range<Token>> map = calculator.getRangeFetchMap();
validateRange(rangesWithSources, map);
//We should validate that it streamed from only non local host and only one range
Assert.assertEquals(1, map.asMap().keySet().size());
assertArrays(Arrays.asList(generateRange(41, 50)), map.asMap().get(InetAddress.getByName("127.0.0.2")));
}
@Test
public void testForEmptyGraph() throws Exception
{
Multimap<Range<Token>, InetAddress> rangesWithSources = HashMultimap.create();
addRangeAndSources(rangesWithSources, 1, 10, "127.0.0.1");
addRangeAndSources(rangesWithSources, 11, 20, "127.0.0.1");
addRangeAndSources(rangesWithSources, 21, 30, "127.0.0.1");
addRangeAndSources(rangesWithSources, 31, 40, "127.0.0.1");
addRangeAndSources(rangesWithSources, 41, 50, "127.0.0.1");
RangeFetchMapCalculator calculator = new RangeFetchMapCalculator(rangesWithSources, new ArrayList<RangeStreamer.ISourceFilter>(), "Test");
Multimap<InetAddress, Range<Token>> map = calculator.getRangeFetchMap();
//All ranges map to local host so we will not stream anything.
Assert.assertTrue(map.isEmpty());
}
@Test
public void testWithNoSourceWithLocal() throws Exception
{
Multimap<Range<Token>, InetAddress> rangesWithSources = HashMultimap.create();
addRangeAndSources(rangesWithSources, 1, 10, "127.0.0.1", "127.0.0.5");
addRangeAndSources(rangesWithSources, 11, 20, "127.0.0.2");
addRangeAndSources(rangesWithSources, 21, 30, "127.0.0.3");
//Return false for all except 127.0.0.5
final RangeStreamer.ISourceFilter filter = new RangeStreamer.ISourceFilter()
{
public boolean shouldInclude(InetAddress endpoint)
{
try
{
if (endpoint.equals(InetAddress.getByName("127.0.0.5")))
return false;
else
return true;
}
catch (UnknownHostException e)
{
return true;
}
}
};
RangeFetchMapCalculator calculator = new RangeFetchMapCalculator(rangesWithSources, Arrays.asList(filter), "Test");
Multimap<InetAddress, Range<Token>> map = calculator.getRangeFetchMap();
validateRange(rangesWithSources, map);
//We should validate that it streamed from only non local host and only one range
Assert.assertEquals(2, map.asMap().keySet().size());
assertArrays(Arrays.asList(generateRange(11, 20)), map.asMap().get(InetAddress.getByName("127.0.0.2")));
assertArrays(Arrays.asList(generateRange(21, 30)), map.asMap().get(InetAddress.getByName("127.0.0.3")));
}
@Test (expected = IllegalStateException.class)
public void testWithNoLiveSource() throws Exception
{
Multimap<Range<Token>, InetAddress> rangesWithSources = HashMultimap.create();
addRangeAndSources(rangesWithSources, 1, 10, "127.0.0.5");
addRangeAndSources(rangesWithSources, 11, 20, "127.0.0.2");
addRangeAndSources(rangesWithSources, 21, 30, "127.0.0.3");
final RangeStreamer.ISourceFilter allDeadFilter = new RangeStreamer.ISourceFilter()
{
public boolean shouldInclude(InetAddress endpoint)
{
return false;
}
};
RangeFetchMapCalculator calculator = new RangeFetchMapCalculator(rangesWithSources, Arrays.asList(allDeadFilter), "Test");
calculator.getRangeFetchMap();
}
@Test
public void testForLocalDC() throws Exception
{
Multimap<Range<Token>, InetAddress> rangesWithSources = HashMultimap.create();
addRangeAndSources(rangesWithSources, 1, 10, "127.0.0.1", "127.0.0.3", "127.0.0.53");
addRangeAndSources(rangesWithSources, 11, 20, "127.0.0.1", "127.0.0.3", "127.0.0.57");
addRangeAndSources(rangesWithSources, 21, 30, "127.0.0.2", "127.0.0.59", "127.0.0.61");
RangeFetchMapCalculator calculator = new RangeFetchMapCalculator(rangesWithSources, new ArrayList<>(), "Test");
Multimap<InetAddress, Range<Token>> map = calculator.getRangeFetchMap();
validateRange(rangesWithSources, map);
Assert.assertEquals(2, map.asMap().size());
//Should have streamed from local DC endpoints
assertArrays(Arrays.asList(generateRange(21, 30)), map.asMap().get(InetAddress.getByName("127.0.0.2")));
assertArrays(Arrays.asList(generateRange(1, 10), generateRange(11, 20)), map.asMap().get(InetAddress.getByName("127.0.0.3")));
}
@Test
public void testForRemoteDC() throws Exception
{
Multimap<Range<Token>, InetAddress> rangesWithSources = HashMultimap.create();
addRangeAndSources(rangesWithSources, 1, 10, "127.0.0.3", "127.0.0.51");
addRangeAndSources(rangesWithSources, 11, 20, "127.0.0.3", "127.0.0.55");
addRangeAndSources(rangesWithSources, 21, 30, "127.0.0.2", "127.0.0.59");
//Reject only 127.0.0.3 and accept everyone else
final RangeStreamer.ISourceFilter localHostFilter = new RangeStreamer.ISourceFilter()
{
public boolean shouldInclude(InetAddress endpoint)
{
try
{
if (endpoint.equals(InetAddress.getByName("127.0.0.3")))
return false;
else
return true;
}
catch (UnknownHostException e)
{
return true;
}
}
};
RangeFetchMapCalculator calculator = new RangeFetchMapCalculator(rangesWithSources, Arrays.asList(localHostFilter), "Test");
Multimap<InetAddress, Range<Token>> map = calculator.getRangeFetchMap();
validateRange(rangesWithSources, map);
Assert.assertEquals(3, map.asMap().size());
//Should have streamed from remote DC endpoint
assertArrays(Arrays.asList(generateRange(1, 10)), map.asMap().get(InetAddress.getByName("127.0.0.51")));
assertArrays(Arrays.asList(generateRange(11, 20)), map.asMap().get(InetAddress.getByName("127.0.0.55")));
assertArrays(Arrays.asList(generateRange(21, 30)), map.asMap().get(InetAddress.getByName("127.0.0.2")));
}
private void assertArrays(Collection<Range<Token>> expected, Collection<Range<Token>> result)
{
Assert.assertEquals(expected.size(), result.size());
Assert.assertTrue(result.containsAll(expected));
}
private void validateRange(Multimap<Range<Token>, InetAddress> rangesWithSources, Multimap<InetAddress, Range<Token>> result)
{
for (Map.Entry<InetAddress, Range<Token>> entry : result.entries())
{
Assert.assertTrue(rangesWithSources.get(entry.getValue()).contains(entry.getKey()));
}
}
private void addRangeAndSources(Multimap<Range<Token>, InetAddress> rangesWithSources, int left, int right, String... hosts) throws UnknownHostException
{
for (InetAddress endpoint : makeAddrs(hosts))
{
rangesWithSources.put(generateRange(left, right), endpoint);
}
}
private Collection<InetAddress> makeAddrs(String... hosts) throws UnknownHostException
{
ArrayList<InetAddress> addrs = new ArrayList<InetAddress>(hosts.length);
for (String host : hosts)
addrs.add(InetAddress.getByName(host));
return addrs;
}
private Range<Token> generateRange(int left, int right)
{
return new Range<Token>(new RandomPartitioner.BigIntegerToken(String.valueOf(left)), new RandomPartitioner.BigIntegerToken(String.valueOf(right)));
}
}