/* * 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.ketama; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.net.HostAndPort; import com.spotify.folsom.RawMemcacheClient; import org.junit.Before; import org.junit.Test; import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ContinuumTest { private static final HostAndPort ADDRESS1 = HostAndPort.fromParts("127.0.0.1", 11211); private static final HostAndPort ADDRESS2 = HostAndPort.fromParts("127.0.0.1", 11212); private static final HostAndPort ADDRESS3 = HostAndPort.fromParts("127.0.0.1", 11213); private static final HostAndPort ADDRESS4 = HostAndPort.fromParts("127.0.0.1", 11214); private static final HostAndPort ADDRESS5 = HostAndPort.fromParts("127.0.0.1", 11215); private static final RawMemcacheClient CLIENT1 = mock(RawMemcacheClient.class); private static final RawMemcacheClient CLIENT2 = mock(RawMemcacheClient.class); private static final RawMemcacheClient CLIENT3 = mock(RawMemcacheClient.class); private static final RawMemcacheClient CLIENT4 = mock(RawMemcacheClient.class); private static final RawMemcacheClient CLIENT5 = mock(RawMemcacheClient.class); private static final AddressAndClient AAC1 = new AddressAndClient(ADDRESS1, CLIENT1); private static final AddressAndClient AAC2 = new AddressAndClient(ADDRESS2, CLIENT2); private static final AddressAndClient AAC3 = new AddressAndClient(ADDRESS3, CLIENT3); private static final AddressAndClient AAC4 = new AddressAndClient(ADDRESS4, CLIENT4); private static final AddressAndClient AAC5 = new AddressAndClient(ADDRESS5, CLIENT5); private static final String KEY1 = "key1"; private static final String KEY2 = "key2"; private static final String KEY3 = "key3"; private static final String KEY4 = "key4"; private static final String KEY5 = "key5"; private static final String KEY6 = "key6"; private static final String KEY7 = "key7"; private static final String KEY8 = "key8"; private static final String KEY9 = "key9"; private static final String KEY10 = "key10"; private static final String KEY11 = "key11"; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(CLIENT1.isConnected()).thenReturn(true); when(CLIENT2.isConnected()).thenReturn(true); when(CLIENT3.isConnected()).thenReturn(true); when(CLIENT4.isConnected()).thenReturn(true); when(CLIENT5.isConnected()).thenReturn(true); } @Test public void testSingleClient() { List<AddressAndClient> clients = ImmutableList.of(AAC1); Continuum c = new Continuum(clients); List<RawMemcacheClient> actual = Arrays.asList( c.findClient(bytes(KEY1)), c.findClient(bytes(KEY2)), c.findClient(bytes(KEY3))); // all keys to the same client List<RawMemcacheClient> expected = Arrays.asList(CLIENT1, CLIENT1, CLIENT1); assertEquals(expected, actual); } @Test public void testMultipleClients() { List<AddressAndClient> clients = ImmutableList.of(AAC1, AAC2, AAC3); Continuum c = new Continuum(clients); List<RawMemcacheClient> actual = Arrays.asList( c.findClient(bytes(KEY1)), c.findClient(bytes(KEY2)), c.findClient(bytes(KEY3)), c.findClient(bytes(KEY4)), c.findClient(bytes(KEY5)), c.findClient(bytes(KEY6))); List<RawMemcacheClient> expected = Arrays.asList( CLIENT1, CLIENT1, CLIENT2, CLIENT3, CLIENT2, CLIENT1); assertEquals(expected, actual); } @Test public void testMultipleClientsOneDisconnected() { List<AddressAndClient> clients = ImmutableList.of(AAC1, AAC2, AAC3, AAC4, AAC5); Continuum c = new Continuum(clients); List<RawMemcacheClient> actual = Arrays.asList( c.findClient(bytes(KEY1)), c.findClient(bytes(KEY2)), c.findClient(bytes(KEY3)), c.findClient(bytes(KEY4)), c.findClient(bytes(KEY5)), c.findClient(bytes(KEY6)), c.findClient(bytes(KEY7)), c.findClient(bytes(KEY8)), c.findClient(bytes(KEY9)), c.findClient(bytes(KEY10)) ); List<RawMemcacheClient> expected = Arrays.asList( CLIENT1, CLIENT5, CLIENT2, CLIENT4, CLIENT2, CLIENT1, CLIENT5, CLIENT1, CLIENT1, CLIENT4); assertEquals(expected, actual); when(CLIENT1.isConnected()).thenReturn(false); actual = Arrays.asList( c.findClient(bytes(KEY1)), c.findClient(bytes(KEY2)), c.findClient(bytes(KEY3)), c.findClient(bytes(KEY4)), c.findClient(bytes(KEY5)), c.findClient(bytes(KEY6)), c.findClient(bytes(KEY7)), c.findClient(bytes(KEY8)), c.findClient(bytes(KEY9)), c.findClient(bytes(KEY10)) ); expected = Arrays.asList( CLIENT4, // 1 -> 4 CLIENT5, CLIENT2, CLIENT4, CLIENT2, CLIENT5, // 1 -> 5 CLIENT5, CLIENT2, // 1 -> 2 CLIENT5, // 1 -> 5 CLIENT4); assertEquals(expected, actual); } @Test public void testWrap() { // key321 hashes to a value bigger than any node in the ring, thus we must wrap around the ring // key477 is smaller than any node in the ring, and thus also should get the same client final List<AddressAndClient> clients = ImmutableList.of(AAC1, AAC2, AAC3); final Continuum c = new Continuum(clients); List<RawMemcacheClient> actual = Arrays.asList(c.findClient(bytes("key321")), c.findClient(bytes("key477"))); List<RawMemcacheClient> expected = Arrays.asList(CLIENT2, CLIENT3); assertEquals(expected, actual); } @Test public void testWrapDisconnected() { // key1561 hashes to CLIENT3, which is the last node in the rung, but is disconnected. // thus we must wrap around correctly in the disconnected case final List<AddressAndClient> clients = ImmutableList.of(AAC1, AAC2, AAC3); final Continuum c = new Continuum(clients); assertSame(CLIENT1, c.findClient(bytes("key1561"))); when(CLIENT1.isConnected()).thenReturn(false); assertSame(CLIENT2, c.findClient(bytes("key1561"))); } private static byte[] bytes(String key) { return key.getBytes(Charsets.US_ASCII); } }