/*
* Copyright (c) 2015 Spotify AB
*
* Licensed 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 com.spotify.folsom;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.net.HostAndPort;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.spotify.dns.DnsSrvResolver;
import com.spotify.dns.LookupResult;
import com.spotify.folsom.client.NoopMetrics;
import com.spotify.folsom.client.Utils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class SrvKetamaIntegrationTest {
private static KetamaIntegrationTest.Servers servers;
private MemcacheClient<String> client;
@BeforeClass
public static void setUpClass() throws Exception {
servers = new KetamaIntegrationTest.Servers(3, false);
}
@AfterClass
public static void tearDownClass() throws Exception {
servers.stop();
}
@Before
public void setUp() throws Exception {
MemcacheClientBuilder<String> builder = MemcacheClientBuilder.newStringClient()
.withSRVRecord("memcached.srv")
.withSrvResolver(new DnsSrvResolver() {
@Override
public List<LookupResult> resolve(String s) {
return toResult(servers.getAddresses());
}
})
.withSRVShutdownDelay(1000)
.withMaxOutstandingRequests(10000)
.withMetrics(NoopMetrics.INSTANCE)
.withRetry(false)
.withReplyExecutor(Utils.SAME_THREAD_EXECUTOR)
.withRequestTimeoutMillis(10 * 1000);
client = builder.connectAscii();
KetamaIntegrationTest.allClientsConnected(client);
servers.flush();
}
public static List<LookupResult> toResult(List<HostAndPort> addresses) {
return Lists.transform(addresses, new Function<HostAndPort, LookupResult>() {
@Override
public LookupResult apply(HostAndPort input) {
return LookupResult.create(input.getHostText(), input.getPort(), 100, 100, 100);
}
});
}
@After
public void tearDown() throws Exception {
client.shutdown();
ConnectFuture.disconnectFuture(client).get();
}
@Test
public void testSetGet() throws Exception {
List<ListenableFuture<?>> futures = Lists.newArrayList();
final int numKeys = 1000;
for (int i = 0; i < numKeys; i++) {
ListenableFuture<MemcacheStatus> future = client.set("key-" + i, "value-" + i, 0);
futures.add(future);
// Do this to avoid making the embedded memcached sad
if (i % 10 == 0) {
future.get();
}
}
Futures.allAsList(futures).get();
futures.clear();
for (int i = 0; i < numKeys; i++) {
ListenableFuture<String> future = client.get("key-" + i);
futures.add(future);
// Do this to avoid making the embedded memcached sad
if (i % 10 == 0) {
future.get();
}
}
for (int i = 0; i < numKeys; i++) {
assertEquals("value-" + i, futures.get(i).get());
}
futures.clear();
servers.getInstance(1).stop();
// TODO: make this prettier
while (client.numActiveConnections() == client.numTotalConnections()) {
Thread.sleep(1);
}
assertTrue(client.numActiveConnections() == client.numTotalConnections() - 1);
for (int i = 0; i < numKeys; i++) {
ListenableFuture<String> future = client.get("key-" + i);
futures.add(future);
// Do this to avoid making the embedded memcached sad
if (i % 10 == 0) {
future.get();
}
}
int misses = 0;
for (int i = 0; i < numKeys; i++) {
misses += futures.get(i).get() == null ? 1 : 0;
}
// About 1/3 should be misses.
// Due to random ports in the embedded server, this is actually somewhat non-deterministic.
// This is why we use a large number of keys.
double missWithPerfectDistribution = numKeys / servers.getAddresses().size();
double diff = Math.abs(misses - missWithPerfectDistribution);
double relativeDiff = diff / numKeys;
assertTrue("Misses: " + misses, relativeDiff < 0.2);
}
}