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