/* * Copyright (c) 2014-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.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.net.HostAndPort; 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 org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @RunWith(Parameterized.class) public class KetamaIntegrationTest { @Parameterized.Parameters(name = "{0}") public static Collection<Object[]> data() throws Exception { ArrayList<Object[]> res = Lists.newArrayList(); res.add(new Object[]{"ascii"}); res.add(new Object[]{"binary"}); return res; } @Parameterized.Parameter(0) public String protocol; private static Servers asciiServers; private static Servers binaryServers; private Servers servers; private MemcacheClient<String> client; @BeforeClass public static void setUpClass() throws Exception { asciiServers = new Servers(3, false); binaryServers = new Servers(3, true); } @AfterClass public static void tearDownClass() throws Exception { asciiServers.stop(); binaryServers.stop(); } public static void allClientsConnected(final MemcacheClient<?> client) throws Exception { final CountDownLatch done = new CountDownLatch(1); client.registerForConnectionChanges(new ConnectionChangeListener() { @Override public void connectionChanged(ObservableClient ignored) { if (client.numActiveConnections() == client.numTotalConnections()) { done.countDown(); } } }); if (!done.await(10, TimeUnit.SECONDS)) { throw new RuntimeException("Failed to connect to all cluster nodes before timeout"); } } @Before public void setUp() throws Exception { boolean ascii; if (protocol.equals("ascii")) { ascii = true; servers = asciiServers; } else if (protocol.equals("binary")) { ascii = false; servers = binaryServers; } else { throw new IllegalArgumentException(protocol); } MemcacheClientBuilder<String> builder = MemcacheClientBuilder.newStringClient() .withAddresses(servers.getAddresses()) .withConnections(1) .withMaxOutstandingRequests(100) .withMetrics(NoopMetrics.INSTANCE) .withRetry(false) .withReplyExecutor(Utils.SAME_THREAD_EXECUTOR) .withRequestTimeoutMillis(10 * 1000); if (ascii) { client = builder.connectAscii(); } else { client = builder.connectBinary(); } allClientsConnected(client); System.out.println("Using client: " + client + ", protocol: " + protocol); servers.flush(); } @After public void tearDown() throws Exception { client.shutdown(); } protected static final String KEY1 = "folsomtest:key1"; protected static final String KEY2 = "folsomtest:key2"; protected static final String KEY3 = "folsomtest:key3"; protected static final String VALUE1 = "val1"; protected static final String VALUE2 = "val2"; protected static final String NUMERIC_VALUE = "123"; public static final List<String> ALL_KEYS = ImmutableList.of(KEY1, KEY2, KEY3); protected static final int TTL = Integer.MAX_VALUE; @Test public void testSetGet() throws Exception { client.set(KEY1, VALUE1, TTL).get(); assertEquals(VALUE1, client.get(KEY1).get()); } @Test public void testLargeSet() throws Exception { String value = Collections.nCopies(10000, "Hello world ").toString(); client.set(KEY1, value, TTL).get(); assertEquals(value, client.get(KEY1).get()); } @Test public void testAppend() throws Exception { client.set(KEY1, VALUE1, TTL).get(); client.append(KEY1, VALUE2).get(); assertEquals(VALUE1 + VALUE2, client.get(KEY1).get()); } @Test public void testAppendMissing() throws Throwable { IntegrationTest.checkStatus(client.append(KEY1, VALUE2), MemcacheStatus.ITEM_NOT_STORED); } @Test public void testPrependMissing() throws Throwable { IntegrationTest.checkStatus(client.prepend(KEY1, VALUE2), MemcacheStatus.ITEM_NOT_STORED); } @Test public void testPrepend() throws Exception { client.set(KEY1, VALUE1, TTL).get(); client.prepend(KEY1, VALUE2).get(); assertEquals(VALUE2 + VALUE1, client.get(KEY1).get()); } @Test public void testAddGet() throws Exception { client.add(KEY1, VALUE1, TTL).get(); assertEquals(VALUE1, client.get(KEY1).get()); } @Test public void testAddKeyExists() throws Throwable { client.set(KEY1, VALUE1, TTL).get(); IntegrationTest.checkStatus(client.add(KEY1, VALUE1, TTL), MemcacheStatus.ITEM_NOT_STORED); } @Test public void testReplaceGet() throws Exception { client.set(KEY1, VALUE1, TTL).get(); client.replace(KEY1, VALUE2, TTL).get(); assertEquals(VALUE2, client.get(KEY1).get()); } @Test public void testMultiGetAllKeysExistsIterable() throws Throwable { client.set(KEY1, VALUE1, TTL).get(); client.set(KEY2, VALUE2, TTL).get(); assertEquals(asList(VALUE1, VALUE2), client.get(asList(KEY1, KEY2)).get()); } @Test public void testDeleteKeyNotExist() throws Throwable { IntegrationTest.checkKeyNotFound(client.delete(KEY1)); } @Test public void testMultiget() throws Exception { for (int i = 0; i < 100; i++) { client.set("key-" + i, "value-" + i, 0).get(); } for (int i = 0; i < 100; i++) { assertEquals("value-" + i, client.get("key-" + i).get()); } int listSize = 10; for (int i = 0; i <= 100 - listSize; i++) { List<String> keys = Lists.newArrayList(); List<String> expectedValues = Lists.newArrayList(); for (int j = 0; j < listSize; j++) { keys.add("key-" + (i + j)); expectedValues.add("value-" + (i + j)); } assertEquals(expectedValues, client.get(keys).get()); } for (EmbeddedServer daemon : servers.daemons) { assertTrue(daemon.getCache().getCurrentItems() > 10); } } public static class Servers { private final List<EmbeddedServer> daemons; private final List<HostAndPort> addresses; public Servers(int instances, boolean binary) { daemons = Lists.newArrayList(); addresses = Lists.newArrayList(); for (int i = 0; i < instances; i++) { EmbeddedServer daemon = new EmbeddedServer(binary); daemons.add(daemon); addresses.add(HostAndPort.fromParts("localhost", daemon.getPort())); } } public void stop() { for (EmbeddedServer daemon : daemons) { daemon.stop(); } } public List<HostAndPort> getAddresses() { return addresses; } public void flush() { for (EmbeddedServer daemon : daemons) { daemon.flush(); } } public EmbeddedServer getInstance(int i) { return daemons.get(i); } } }