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.internal.BegunTransactionalActivityHandler;
9 import com.atlassian.vcache.internal.RequestContext;
10 import com.atlassian.vcache.internal.core.DefaultRequestContext;
11 import com.atlassian.vcache.internal.core.DefaultTransactionControlManager;
12 import com.atlassian.vcache.internal.core.PlainExternalCacheKeyGenerator;
13 import com.atlassian.vcache.internal.core.metrics.DefaultMetricsCollector;
14 import com.atlassian.vcache.internal.core.metrics.MetricsCollector;
15 import com.atlassian.vcache.internal.test.LoggingTestWatcher;
16 import com.google.common.collect.Maps;
17 import net.spy.memcached.MemcachedClientIF;
18 import org.junit.Before;
19 import org.junit.Rule;
20 import org.junit.Test;
21 import org.junit.runner.RunWith;
22 import org.mockito.Mock;
23 import org.mockito.runners.MockitoJUnitRunner;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import java.time.Duration;
28 import java.util.Map;
29 import java.util.Optional;
30 import java.util.concurrent.CompletionStage;
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 @RunWith(MockitoJUnitRunner.class)
46 public class MemcachedTransactionalExternalCacheTest {
47 private static final Logger log = LoggerFactory.getLogger(MemcachedTransactionalExternalCacheTest.class);
48
49 private static final int DEFAULT_TTL = 11;
50
51 @Rule
52 public LoggingTestWatcher watcher = new LoggingTestWatcher(log);
53
54 @Mock
55 private MemcachedClientIF memClient;
56
57 @Mock
58 private BegunTransactionalActivityHandler begunTransactionalActivityHandler;
59
60 private RequestContext requestContext = new DefaultRequestContext(() -> "tenant-id");
61 private MetricsCollector metricsCollector = new DefaultMetricsCollector(() -> requestContext);
62
63 @Mock
64 private Future<Boolean> booleanFuture;
65
66 private MemcachedTransactionalExternalCache<String> cache;
67
68 @Before
69 public void init() {
70 final ExternalCacheSettings settings = new ExternalCacheSettingsBuilder()
71 .entryGrowthRateHint(ChangeRate.LOW_CHANGE)
72 .entryCountHint(5)
73 .defaultTtl(Duration.ofSeconds(DEFAULT_TTL))
74 .dataChangeRateHint(ChangeRate.HIGH_CHANGE)
75 .build();
76
77 cache = new MemcachedTransactionalExternalCache<>(
78 Utils.defaultServiceSettingsBuilder(() -> memClient, Duration.ofSeconds(1)).build(),
79 () -> requestContext,
80 new PlainExternalCacheKeyGenerator("prodid"),
81 "mocked",
82 StringMarshalling.pair(),
83 settings,
84 new DefaultTransactionControlManager(metricsCollector, begunTransactionalActivityHandler),
85 metricsCollector);
86 }
87
88 @Test
89 public void get_missing() throws Exception {
90 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
91 when(memClient.get(eq("prodid::tenant-id::mocked::3::missing"))).thenReturn(null);
92
93 final CompletionStage<Optional<String>> f1 = cache.get("missing");
94
95 assertThat(f1, successfulWith(is(Optional.empty())));
96
97 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
98 verify(memClient).get(eq("prodid::tenant-id::mocked::3::missing"));
99 verifyNoMoreInteractions(memClient);
100 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
101 }
102
103 @Test
104 public void removeAll_get() {
105 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
106
107 cache.removeAll();
108
109 verifyNoMoreInteractions(memClient);
110
111 final CompletionStage<Optional<String>> f1 = cache.get("missing");
112
113 assertThat(f1, successfulWith(is(Optional.empty())));
114
115 verifyNoMoreInteractions(memClient);
116 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
117 }
118
119 @Test
120 public void removeAll_put_get() {
121 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
122
123 cache.removeAll();
124
125 verifyNoMoreInteractions(memClient);
126
127 cache.put("mary", "lamb", PutPolicy.PUT_ALWAYS);
128
129 final CompletionStage<Optional<String>> f1 = cache.get("mary");
130 assertThat(f1, successfulWith(is(Optional.of("lamb"))));
131
132 verifyNoMoreInteractions(memClient);
133
134 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
135 }
136
137 @Test
138 public void removeAll_getBulk() throws Exception {
139 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
140
141 cache.removeAll();
142
143 verifyNoMoreInteractions(memClient);
144
145 final CompletionStage<Map<String, Optional<String>>> get1 = cache.getBulk("key-1", "key-2");
146
147 assertThat(get1, successful());
148 assertThat(unsafeJoin(get1).keySet(), containsInAnyOrder("key-1", "key-2"));
149
150 assertThat(unsafeJoin(get1).values(), containsInAnyOrder(Optional.empty(), Optional.empty()));
151
152 verifyNoMoreInteractions(memClient);
153 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
154 }
155
156 @Test
157 public void removeAll_getBulkFactory() throws Exception {
158 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
159
160 cache.removeAll();
161
162 verifyNoMoreInteractions(memClient);
163
164 final CompletionStage<Map<String, String>> get1 = cache.getBulk(
165 keys -> Maps.asMap(keys, k -> k + "-1"), "key-1", "key-2");
166
167 assertThat(get1, successful());
168 assertThat(unsafeJoin(get1).keySet(), containsInAnyOrder("key-1", "key-2"));
169 assertThat(unsafeJoin(get1).values(), containsInAnyOrder("key-1-1", "key-2-1"));
170
171 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
172 verifyNoMoreInteractions(memClient);
173 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
174 }
175
176 @Test
177 public void remove_get() {
178 cache.remove("missing");
179
180 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
181
182 final CompletionStage<Optional<String>> f1 = cache.get("missing");
183
184 assertThat(f1, successfulWith(is(Optional.empty())));
185
186 verifyNoMoreInteractions(memClient);
187 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
188 }
189
190 @Test
191 public void put_get() {
192 cache.put("lockwood", "brand", PutPolicy.PUT_ALWAYS);
193
194 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
195
196 final CompletionStage<Optional<String>> f1 = cache.get("lockwood");
197
198 assertThat(f1, successfulWith(is(Optional.of("brand"))));
199
200 verifyNoMoreInteractions(memClient);
201 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
202 }
203
204 @Test
205 public void put_removeAll_getSupplier() {
206 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
207
208 cache.put("six", "pack", PutPolicy.ADD_ONLY);
209
210 verifyNoMoreInteractions(memClient);
211
212 final CompletionStage<Optional<String>> get1 = cache.get("six");
213
214 assertThat(get1, successfulWith(is(Optional.of("pack"))));
215
216 verifyNoMoreInteractions(memClient);
217
218 cache.removeAll();
219
220 verifyNoMoreInteractions(memClient);
221
222 final CompletionStage<String> get2 = cache.get("six", () -> "times");
223
224 assertThat(get2, successfulWith(is("times")));
225 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
226 verifyNoMoreInteractions(memClient);
227 }
228
229 @Test
230 public void removeAll_put_getSupplier() {
231 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
232
233 cache.put("six", "pack", PutPolicy.ADD_ONLY);
234
235 verifyNoMoreInteractions(memClient);
236
237 final CompletionStage<Optional<String>> get1 = cache.get("six");
238
239 assertThat(get1, successfulWith(is(Optional.of("pack"))));
240
241 verifyNoMoreInteractions(memClient);
242
243 cache.removeAll();
244
245 verifyNoMoreInteractions(memClient);
246
247 cache.put("mary", "lamb", PutPolicy.PUT_ALWAYS);
248
249 final CompletionStage<String> get2 = cache.get("six", () -> "times");
250
251 assertThat(get2, successfulWith(is("times")));
252 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
253 verifyNoMoreInteractions(memClient);
254 }
255
256
257 @Test
258 public void put_sync_sync() throws Exception {
259 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
260
261 cache.put("aws", "monopoly", PutPolicy.ADD_ONLY);
262
263 verifyNoMoreInteractions(memClient);
264
265 when(booleanFuture.get()).thenReturn(true);
266 when(memClient.add(
267 eq("prodid::tenant-id::mocked::3::aws"), eq(DEFAULT_TTL),
268 eq(new StringMarshalling().marshallToBytes("monopoly"))))
269 .thenReturn(booleanFuture);
270
271 cache.transactionSync();
272
273 verify(memClient).add(
274 eq("prodid::tenant-id::mocked::3::aws"), eq(DEFAULT_TTL),
275 eq(new StringMarshalling().marshallToBytes("monopoly")));
276 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
277 verifyNoMoreInteractions(memClient);
278
279 cache.transactionSync();
280 verifyNoMoreInteractions(memClient);
281 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
282 }
283 }