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 () -> memClient,
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 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
110 verifyNoMoreInteractions(memClient);
111
112
113 final CompletionStage<Optional<String>> f1 = cache.get("missing");
114
115 assertThat(f1, successfulWith(is(Optional.empty())));
116
117 verifyNoMoreInteractions(memClient);
118 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
119 }
120
121 @Test
122 public void removeAll_put_get() {
123 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
124
125 cache.removeAll();
126
127 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
128 verifyNoMoreInteractions(memClient);
129
130 cache.put("mary", "lamb", PutPolicy.PUT_ALWAYS);
131
132 final CompletionStage<Optional<String>> f1 = cache.get("mary");
133 assertThat(f1, successfulWith(is(Optional.of("lamb"))));
134
135 verifyNoMoreInteractions(memClient);
136
137 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
138 }
139
140 @Test
141 public void removeAll_getBulk() throws Exception {
142 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
143
144 cache.removeAll();
145
146 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
147 verifyNoMoreInteractions(memClient);
148
149 final CompletionStage<Map<String, Optional<String>>> get1 = cache.getBulk("key-1", "key-2");
150
151 assertThat(get1, successful());
152 assertThat(unsafeJoin(get1).keySet(), containsInAnyOrder("key-1", "key-2"));
153
154 assertThat(unsafeJoin(get1).values(), containsInAnyOrder(Optional.empty(), Optional.empty()));
155
156 verifyNoMoreInteractions(memClient);
157 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
158 }
159
160 @Test
161 public void removeAll_getBulkFactory() throws Exception {
162 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
163
164 cache.removeAll();
165
166 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
167 verifyNoMoreInteractions(memClient);
168
169 final CompletionStage<Map<String, String>> get1 = cache.getBulk(
170 keys -> Maps.asMap(keys, k -> k + "-1"), "key-1", "key-2");
171
172 assertThat(get1, successful());
173 assertThat(unsafeJoin(get1).keySet(), containsInAnyOrder("key-1", "key-2"));
174 assertThat(unsafeJoin(get1).values(), containsInAnyOrder("key-1-1", "key-2-1"));
175
176 verifyNoMoreInteractions(memClient);
177 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
178 }
179
180 @Test
181 public void remove_get() {
182 cache.remove("missing");
183
184 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
185
186 final CompletionStage<Optional<String>> f1 = cache.get("missing");
187
188 assertThat(f1, successfulWith(is(Optional.empty())));
189
190 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
191 verifyNoMoreInteractions(memClient);
192 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
193 }
194
195 @Test
196 public void put_get() {
197 cache.put("lockwood", "brand", PutPolicy.PUT_ALWAYS);
198
199 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
200
201 final CompletionStage<Optional<String>> f1 = cache.get("lockwood");
202
203 assertThat(f1, successfulWith(is(Optional.of("brand"))));
204
205 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
206 verifyNoMoreInteractions(memClient);
207 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
208 }
209
210 @Test
211 public void put_removeAll_getSupplier() {
212 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
213
214 cache.put("six", "pack", PutPolicy.ADD_ONLY);
215
216 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
217 verifyNoMoreInteractions(memClient);
218
219 final CompletionStage<Optional<String>> get1 = cache.get("six");
220
221 assertThat(get1, successfulWith(is(Optional.of("pack"))));
222
223 verifyNoMoreInteractions(memClient);
224
225 cache.removeAll();
226
227 verifyNoMoreInteractions(memClient);
228
229 final CompletionStage<String> get2 = cache.get("six", () -> "times");
230
231 assertThat(get2, successfulWith(is("times")));
232 verifyNoMoreInteractions(memClient);
233 }
234
235 @Test
236 public void removeAll_put_getSupplier() {
237 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
238
239 cache.put("six", "pack", PutPolicy.ADD_ONLY);
240
241 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
242 verifyNoMoreInteractions(memClient);
243
244 final CompletionStage<Optional<String>> get1 = cache.get("six");
245
246 assertThat(get1, successfulWith(is(Optional.of("pack"))));
247
248 verifyNoMoreInteractions(memClient);
249
250 cache.removeAll();
251
252 verifyNoMoreInteractions(memClient);
253
254 cache.put("mary", "lamb", PutPolicy.PUT_ALWAYS);
255
256 final CompletionStage<String> get2 = cache.get("six", () -> "times");
257
258 assertThat(get2, successfulWith(is("times")));
259 verifyNoMoreInteractions(memClient);
260 }
261
262
263 @Test
264 public void put_sync_sync() throws Exception {
265 when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
266
267 cache.put("aws", "monopoly", PutPolicy.ADD_ONLY);
268
269 verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
270 verifyNoMoreInteractions(memClient);
271
272 when(booleanFuture.get()).thenReturn(true);
273 when(memClient.add(
274 eq("prodid::tenant-id::mocked::3::aws"), eq(DEFAULT_TTL),
275 eq(new StringMarshalling().marshallToBytes("monopoly"))))
276 .thenReturn(booleanFuture);
277
278 cache.transactionSync();
279
280 verify(memClient).add(
281 eq("prodid::tenant-id::mocked::3::aws"), eq(DEFAULT_TTL),
282 eq(new StringMarshalling().marshallToBytes("monopoly")));
283 verifyNoMoreInteractions(memClient);
284
285 cache.transactionSync();
286 verifyNoMoreInteractions(memClient);
287 verify(begunTransactionalActivityHandler, times(1)).onRequest(requestContext);
288 }
289 }