View Javadoc

1   package com.atlassian.vcache.internal.memcached;
2   
3   import com.atlassian.marshalling.jdk.StringMarshalling;
4   import com.atlassian.vcache.ChangeRate;
5   import com.atlassian.vcache.ExternalCacheSettings;
6   import com.atlassian.vcache.ExternalCacheSettingsBuilder;
7   import com.atlassian.vcache.PutPolicy;
8   import com.atlassian.vcache.StableReadExternalCache;
9   import com.atlassian.vcache.internal.MetricLabel;
10  import com.atlassian.vcache.internal.RequestContext;
11  import com.atlassian.vcache.internal.core.DefaultRequestContext;
12  import com.atlassian.vcache.internal.core.PlainExternalCacheKeyGenerator;
13  import com.atlassian.vcache.internal.core.metrics.CacheType;
14  import com.atlassian.vcache.internal.core.metrics.MetricsRecorder;
15  import com.google.common.collect.Maps;
16  import com.google.common.collect.Sets;
17  import net.spy.memcached.MemcachedClientIF;
18  import org.junit.Before;
19  import org.junit.Test;
20  import org.junit.runner.RunWith;
21  import org.mockito.Mock;
22  import org.mockito.runners.MockitoJUnitRunner;
23  
24  import java.time.Duration;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Map;
28  import java.util.Optional;
29  import java.util.concurrent.CompletionStage;
30  import java.util.concurrent.ExecutionException;
31  import java.util.concurrent.Future;
32  
33  import static com.atlassian.vcache.VCacheUtils.unsafeJoin;
34  import static com.atlassian.vcache.internal.test.CompletionStageSuccessful.successful;
35  import static com.atlassian.vcache.internal.test.CompletionStageSuccessful.successfulWith;
36  import static org.hamcrest.MatcherAssert.assertThat;
37  import static org.hamcrest.Matchers.containsInAnyOrder;
38  import static org.hamcrest.Matchers.is;
39  import static org.mockito.Matchers.eq;
40  import static org.mockito.Mockito.times;
41  import static org.mockito.Mockito.verify;
42  import static org.mockito.Mockito.verifyNoMoreInteractions;
43  import static org.mockito.Mockito.when;
44  
45  @SuppressWarnings("NullableProblems")
46  @RunWith(MockitoJUnitRunner.class)
47  public class MemcachedStableReadExternalCacheTest {
48  
49      private static final int DEFAULT_TTL = 6;
50  
51      @Mock
52      private MemcachedClientIF memClient;
53  
54      @Mock
55      private Future<Boolean> booleanFuture;
56  
57      @Mock
58      private MetricsRecorder metricsRecorder;
59  
60      private RequestContext requestContext = new DefaultRequestContext("tenant-id");
61      private StableReadExternalCache<String> cache;
62  
63      @Before
64      public void init() {
65          final ExternalCacheSettings settings = new ExternalCacheSettingsBuilder()
66                  .entryGrowthRateHint(ChangeRate.LOW_CHANGE)
67                  .entryCountHint(5)
68                  .defaultTtl(Duration.ofSeconds(DEFAULT_TTL))
69                  .dataChangeRateHint(ChangeRate.HIGH_CHANGE)
70                  .build();
71          cache = new MemcachedStableReadExternalCache<>(
72                  () -> memClient,
73                  () -> requestContext,
74                  new PlainExternalCacheKeyGenerator("prodid"),
75                  "mocked",
76                  StringMarshalling.pair(),
77                  settings,
78                  metricsRecorder,
79                  Duration.ofSeconds(5));
80      }
81  
82      @Test
83      public void put_policy_set() throws Exception {
84          when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
85          when(booleanFuture.get()).thenReturn(true);
86          when(memClient.set(
87                  eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL),
88                  eq(new StringMarshalling().marshallToBytes("value"))))
89                  .thenReturn(booleanFuture);
90  
91          final CompletionStage<Boolean> f2 = cache.put("missing", "value", PutPolicy.PUT_ALWAYS);
92  
93          assertThat(f2, successfulWith(is(true)));
94  
95          verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
96          verify(memClient).set(
97                  eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL),
98                  eq(new StringMarshalling().marshallToBytes("value")));
99          verifyNoMoreInteractions(memClient);
100     }
101 
102     @Test
103     public void getBulk_no_keys() throws ExecutionException, InterruptedException {
104         final CompletionStage<Map<String, Optional<String>>> get = cache.getBulk();
105 
106         assertThat(get, successful());
107         assertThat(unsafeJoin(get).isEmpty(), is(true));
108         verifyNoMoreInteractions(memClient);
109     }
110 
111     @Test
112     public void getBulk_function_no_keys() throws ExecutionException, InterruptedException {
113         final CompletionStage<Map<String, String>> get = cache.getBulk(keys -> null);
114 
115         assertThat(get, successful());
116         assertThat(unsafeJoin(get).isEmpty(), is(true));
117         verifyNoMoreInteractions(memClient);
118     }
119 
120     @Test
121     public void remove_no_keys() throws ExecutionException, InterruptedException {
122         final CompletionStage<Void> rm1 = cache.remove();
123 
124         assertThat(rm1, successful());
125         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
126         verifyNoMoreInteractions(memClient);
127     }
128 
129     @Test
130     public void removeAll_getBulk() throws Exception {
131         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
132         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(1), eq(1L))).thenReturn(4L);
133 
134         final CompletionStage<Void> rm1 = cache.removeAll();
135 
136         assertThat(rm1, successful());
137         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
138         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(1), eq(1L));
139         verifyNoMoreInteractions(memClient);
140 
141         final HashSet<String> externalKeys = Sets.newHashSet(
142                 "prodid::tenant-id::mocked::4::key-1", "prodid::tenant-id::mocked::4::key-2");
143         final Map<String, Object> externalResult = new HashMap<>();
144         externalResult.put("prodid::tenant-id::mocked::4::key-1", null);
145         externalResult.put("prodid::tenant-id::mocked::4::key-2", null);
146         when(memClient.getBulk(eq(externalKeys))).thenReturn(externalResult);
147 
148         final CompletionStage<Map<String, Optional<String>>> get1 = cache.getBulk("key-1", "key-2");
149 
150         assertThat(get1, successful());
151         assertThat(unsafeJoin(get1).keySet(), containsInAnyOrder("key-1", "key-2"));
152         //noinspection unchecked
153         assertThat(unsafeJoin(get1).values(), containsInAnyOrder(Optional.empty(), Optional.empty()));
154 
155         verify(memClient).getBulk(eq(externalKeys));
156         verifyNoMoreInteractions(memClient);
157     }
158 
159     @Test
160     public void removeAll_getBulkFactory() throws Exception {
161         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
162         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(1), eq(1L))).thenReturn(4L);
163 
164         final CompletionStage<Void> rm1 = cache.removeAll();
165 
166         assertThat(rm1, successful());
167         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
168         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(1), eq(1L));
169         verifyNoMoreInteractions(memClient);
170 
171         final HashSet<String> externalKeys = Sets.newHashSet(
172                 "prodid::tenant-id::mocked::4::key-1", "prodid::tenant-id::mocked::4::key-2");
173         final Map<String, Object> externalResult = new HashMap<>();
174         externalResult.put(
175                 "prodid::tenant-id::mocked::4::key-1",
176                 new StringMarshalling().marshallToBytes("ice-monkeys"));
177         when(memClient.getBulk(eq(externalKeys))).thenReturn(externalResult);
178         when(memClient.add(
179                 eq("prodid::tenant-id::mocked::4::key-2"),
180                 eq(DEFAULT_TTL),
181                 eq(new StringMarshalling().marshallToBytes("key-2-1")))
182         ).thenReturn(booleanFuture);
183         when(booleanFuture.get()).thenReturn(true);
184 
185         final CompletionStage<Map<String, String>> get1 = cache.getBulk(
186                 keys -> Maps.asMap(keys, k -> k + "-1"), "key-1", "key-2");
187 
188         assertThat(get1, successful());
189         assertThat(unsafeJoin(get1).keySet(), containsInAnyOrder("key-1", "key-2"));
190         assertThat(unsafeJoin(get1).values(), containsInAnyOrder("ice-monkeys", "key-2-1"));
191 
192         verify(memClient).getBulk(eq(externalKeys));
193         verify(memClient).add(
194                 eq("prodid::tenant-id::mocked::4::key-2"),
195                 eq(DEFAULT_TTL),
196                 eq(new StringMarshalling().marshallToBytes("key-2-1")));
197         verifyNoMoreInteractions(memClient);
198     }
199 
200     @Test
201     public void getBulk_metrics() throws Exception {
202         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(4L);
203 
204         final HashSet<String> externalKeys = Sets.newHashSet(
205                 "prodid::tenant-id::mocked::4::key-1", "prodid::tenant-id::mocked::4::key-2");
206         final Map<String, Object> externalResult = new HashMap<>();
207         externalResult.put(
208                 "prodid::tenant-id::mocked::4::key-1",
209                 new StringMarshalling().marshallToBytes("ice-monkeys"));
210         when(memClient.getBulk(eq(externalKeys))).thenReturn(externalResult);
211         when(memClient.add(
212                 eq("prodid::tenant-id::mocked::4::key-2"),
213                 eq(DEFAULT_TTL),
214                 eq(new StringMarshalling().marshallToBytes("key-2-1")))
215         ).thenReturn(booleanFuture);
216         when(booleanFuture.get()).thenReturn(true);
217 
218         final CompletionStage<Map<String, String>> get1 = cache.getBulk(
219                 keys -> Maps.asMap(keys, k -> k + "-1"), "key-1", "key-2");
220 
221         assertThat(get1, successful());
222         assertThat(unsafeJoin(get1).keySet(), containsInAnyOrder("key-1", "key-2"));
223         assertThat(unsafeJoin(get1).values(), containsInAnyOrder("ice-monkeys", "key-2-1"));
224 
225         verify(metricsRecorder, times(2)).record(eq("mocked"), eq(CacheType.EXTERNAL), eq(MetricLabel.NUMBER_OF_REMOTE_GET), eq(1L));
226         verifyNoMoreInteractions(metricsRecorder);
227     }
228 
229     @Test
230     public void get_metrics() throws Exception {
231         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(4L);
232         when(memClient.get(eq("prodid::tenant-id::mocked::4::key-1")))
233                 .thenReturn(new StringMarshalling().marshallToBytes("it exists"));
234 
235         final CompletionStage<String> get1 = cache.get("key-1", () -> "value-1");
236 
237         assertThat(get1, successful());
238 
239         verify(metricsRecorder).record(eq("mocked"), eq(CacheType.EXTERNAL), eq(MetricLabel.NUMBER_OF_REMOTE_GET), eq(1L));
240         verifyNoMoreInteractions(metricsRecorder);
241     }
242 }