blob: 150b99deb98fe80bf5388f689bcbe6130e95c8c8 [file] [log] [blame]
Madan Jampani5e5b3d62016-02-01 16:03:33 -08001/*
2 * Copyright 2016 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.store.primitives.resources.impl;
17
18import io.atomix.resource.ResourceType;
19import static org.junit.Assert.*;
20
21import java.util.Arrays;
22import java.util.ConcurrentModificationException;
23import java.util.List;
Madan Jampani40f022e2016-03-02 21:35:14 -080024import java.util.concurrent.ArrayBlockingQueue;
25import java.util.concurrent.BlockingQueue;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080026import java.util.concurrent.CompletionException;
27import java.util.stream.Collectors;
28
29import org.junit.Test;
30import org.onlab.util.Tools;
Madan Jampani74da78b2016-02-09 21:18:36 -080031import org.onosproject.store.primitives.MapUpdate;
Madan Jampanicadd70b2016-02-08 13:45:43 -080032import org.onosproject.store.primitives.TransactionId;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080033import org.onosproject.store.service.MapEvent;
34import org.onosproject.store.service.MapEventListener;
Madan Jampani74da78b2016-02-09 21:18:36 -080035import org.onosproject.store.service.MapTransaction;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080036import org.onosproject.store.service.Versioned;
37
Madan Jampani40f022e2016-03-02 21:35:14 -080038import com.google.common.base.Throwables;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080039import com.google.common.collect.Sets;
40
41/**
42 * Unit tests for {@link AtomixConsistentMap}.
43 */
Madan Jampani5e5b3d62016-02-01 16:03:33 -080044public class AtomixConsistentMapTest extends AtomixTestBase {
45
46 @Override
47 protected ResourceType resourceType() {
48 return new ResourceType(AtomixConsistentMap.class);
49 }
50
51 /**
52 * Tests various basic map operations.
53 */
54 @Test
55 public void testBasicMapOperations() throws Throwable {
56 basicMapOperationTests(1);
57 clearTests();
58 basicMapOperationTests(2);
59 clearTests();
60 basicMapOperationTests(3);
61 }
62
63 /**
64 * Tests various map compute* operations on different cluster sizes.
65 */
66 @Test
67 public void testMapComputeOperations() throws Throwable {
68 mapComputeOperationTests(1);
69 clearTests();
70 mapComputeOperationTests(2);
71 clearTests();
72 mapComputeOperationTests(3);
73 }
74
75 /**
76 * Tests map event notifications.
77 */
78 @Test
79 public void testMapListeners() throws Throwable {
80 mapListenerTests(1);
81 clearTests();
82 mapListenerTests(2);
83 clearTests();
84 mapListenerTests(3);
85 }
86
87 /**
88 * Tests map transaction commit.
89 */
90 @Test
91 public void testTransactionCommit() throws Throwable {
92 transactionCommitTests(1);
93 clearTests();
94 transactionCommitTests(2);
95 clearTests();
96 transactionCommitTests(3);
97 }
98
99 /**
100 * Tests map transaction rollback.
101 */
102 @Test
103 public void testTransactionRollback() throws Throwable {
104 transactionRollbackTests(1);
105 clearTests();
106 transactionRollbackTests(2);
107 clearTests();
108 transactionRollbackTests(3);
109 }
110
111 protected void basicMapOperationTests(int clusterSize) throws Throwable {
112 createCopycatServers(clusterSize);
113
114 final byte[] rawFooValue = Tools.getBytesUtf8("Hello foo!");
115 final byte[] rawBarValue = Tools.getBytesUtf8("Hello bar!");
116
117 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
118
119 map.isEmpty().thenAccept(result -> {
120 assertTrue(result);
121 }).join();
122
123 map.put("foo", rawFooValue).thenAccept(result -> {
124 assertNull(result);
125 }).join();
126
127 map.size().thenAccept(result -> {
128 assertTrue(result == 1);
129 }).join();
130
131 map.isEmpty().thenAccept(result -> {
132 assertFalse(result);
133 }).join();
134
135 map.putIfAbsent("foo", "Hello foo again!".getBytes()).thenAccept(result -> {
136 assertNotNull(result);
137 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
138 }).join();
139
140 map.putIfAbsent("bar", rawBarValue).thenAccept(result -> {
141 assertNull(result);
142 }).join();
143
144 map.size().thenAccept(result -> {
145 assertTrue(result == 2);
146 }).join();
147
148 map.keySet().thenAccept(result -> {
149 assertTrue(result.size() == 2);
150 assertTrue(result.containsAll(Sets.newHashSet("foo", "bar")));
151 }).join();
152
153 map.values().thenAccept(result -> {
154 assertTrue(result.size() == 2);
155 List<String> rawValues =
156 result.stream().map(v -> Tools.toStringUtf8(v.value())).collect(Collectors.toList());
157 assertTrue(rawValues.contains("Hello foo!"));
158 assertTrue(rawValues.contains("Hello bar!"));
159 }).join();
160
161 map.entrySet().thenAccept(result -> {
162 assertTrue(result.size() == 2);
163 // TODO: check entries
164 }).join();
165
166 map.get("foo").thenAccept(result -> {
167 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
168 }).join();
169
170 map.remove("foo").thenAccept(result -> {
171 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
172 }).join();
173
174 map.containsKey("foo").thenAccept(result -> {
175 assertFalse(result);
176 }).join();
177
178 map.get("foo").thenAccept(result -> {
179 assertNull(result);
180 }).join();
181
182 map.get("bar").thenAccept(result -> {
183 assertNotNull(result);
184 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
185 }).join();
186
187 map.containsKey("bar").thenAccept(result -> {
188 assertTrue(result);
189 }).join();
190
191 map.size().thenAccept(result -> {
192 assertTrue(result == 1);
193 }).join();
194
195 map.containsValue(rawBarValue).thenAccept(result -> {
196 assertTrue(result);
197 }).join();
198
199 map.containsValue(rawFooValue).thenAccept(result -> {
200 assertFalse(result);
201 }).join();
202
203 map.replace("bar", "Goodbye bar!".getBytes()).thenAccept(result -> {
204 assertNotNull(result);
205 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
206 }).join();
207
208 map.replace("foo", "Goodbye foo!".getBytes()).thenAccept(result -> {
209 assertNull(result);
210 }).join();
211
212 // try replace_if_value_match for a non-existent key
213 map.replace("foo", "Goodbye foo!".getBytes(), rawFooValue).thenAccept(result -> {
214 assertFalse(result);
215 }).join();
216
217 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
218 assertTrue(result);
219 }).join();
220
221 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
222 assertFalse(result);
223 }).join();
224
225 Versioned<byte[]> barValue = map.get("bar").join();
226 map.replace("bar", barValue.version(), "Goodbye bar!".getBytes()).thenAccept(result -> {
227 assertTrue(result);
228 }).join();
229
230 map.replace("bar", barValue.version(), rawBarValue).thenAccept(result -> {
231 assertFalse(result);
232 }).join();
233
234 map.clear().join();
235
236 map.size().thenAccept(result -> {
237 assertTrue(result == 0);
238 }).join();
239 }
240
241 public void mapComputeOperationTests(int clusterSize) throws Throwable {
242 createCopycatServers(clusterSize);
243 final byte[] value1 = Tools.getBytesUtf8("value1");
244 final byte[] value2 = Tools.getBytesUtf8("value2");
245 final byte[] value3 = Tools.getBytesUtf8("value3");
246
247 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
248
249 map.computeIfAbsent("foo", k -> value1).thenAccept(result -> {
250 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
251 }).join();
252
253 map.computeIfAbsent("foo", k -> value2).thenAccept(result -> {
254 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
255 }).join();
256
257 map.computeIfPresent("bar", (k, v) -> value2).thenAccept(result -> {
258 assertNull(result);
259 });
260
261 map.computeIfPresent("foo", (k, v) -> value3).thenAccept(result -> {
262 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value3));
263 }).join();
264
265 map.computeIfPresent("foo", (k, v) -> null).thenAccept(result -> {
266 assertNull(result);
267 }).join();
268
269 map.computeIf("foo", v -> v == null, (k, v) -> value1).thenAccept(result -> {
270 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
271 }).join();
272
273 map.compute("foo", (k, v) -> value2).thenAccept(result -> {
274 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value2));
275 }).join();
276 }
277
278
279 protected void mapListenerTests(int clusterSize) throws Throwable {
280 createCopycatServers(clusterSize);
281 final byte[] value1 = Tools.getBytesUtf8("value1");
282 final byte[] value2 = Tools.getBytesUtf8("value2");
283 final byte[] value3 = Tools.getBytesUtf8("value3");
284
285 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
286 TestMapEventListener listener = new TestMapEventListener();
287
288 // add listener; insert new value into map and verify an INSERT event is received.
Madan Jampani40f022e2016-03-02 21:35:14 -0800289 map.addListener(listener).thenCompose(v -> map.put("foo", value1)).join();
290 MapEvent<String, byte[]> event = listener.event();
291 assertNotNull(event);
292 assertEquals(MapEvent.Type.INSERT, event.type());
293 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800294
295 // remove listener and verify listener is not notified.
Madan Jampani40f022e2016-03-02 21:35:14 -0800296 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
297 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800298
299 // add the listener back and verify UPDATE events are received correctly
Madan Jampani40f022e2016-03-02 21:35:14 -0800300 map.addListener(listener).thenCompose(v -> map.put("foo", value3)).join();
301 event = listener.event();
302 assertNotNull(event);
303 assertEquals(MapEvent.Type.UPDATE, event.type());
304 assertTrue(Arrays.equals(value3, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800305
306 // perform a non-state changing operation and verify no events are received.
307 map.putIfAbsent("foo", value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800308 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800309
310 // verify REMOVE events are received correctly.
311 map.remove("foo").join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800312 event = listener.event();
313 assertNotNull(event);
314 assertEquals(MapEvent.Type.REMOVE, event.type());
315 assertTrue(Arrays.equals(value3, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800316
317 // verify compute methods also generate events.
318 map.computeIf("foo", v -> v == null, (k, v) -> value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800319 event = listener.event();
320 assertNotNull(event);
321 assertEquals(MapEvent.Type.INSERT, event.type());
322 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800323
324 map.compute("foo", (k, v) -> value2).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800325 event = listener.event();
326 assertNotNull(event);
327 assertEquals(MapEvent.Type.UPDATE, event.type());
328 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800329
330 map.computeIf("foo", v -> Arrays.equals(v, value2), (k, v) -> null).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800331 event = listener.event();
332 assertNotNull(event);
333 assertEquals(MapEvent.Type.REMOVE, event.type());
334 assertTrue(Arrays.equals(value2, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800335
336 map.removeListener(listener).join();
337 }
338
339 protected void transactionCommitTests(int clusterSize) throws Throwable {
340 createCopycatServers(clusterSize);
341 final byte[] value1 = Tools.getBytesUtf8("value1");
342 final byte[] value2 = Tools.getBytesUtf8("value2");
343
344 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
345 TestMapEventListener listener = new TestMapEventListener();
346
347 map.addListener(listener).join();
348
349 MapUpdate<String, byte[]> update1 =
350 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
351 .withKey("foo")
352 .withValue(value1)
353 .build();
354
Madan Jampani74da78b2016-02-09 21:18:36 -0800355 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800356
Madan Jampanicadd70b2016-02-08 13:45:43 -0800357 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800358 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800359 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800360 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800361
362 map.size().thenAccept(result -> {
363 assertTrue(result == 0);
364 }).join();
365
366 map.get("foo").thenAccept(result -> {
367 assertNull(result);
368 }).join();
369
370 try {
371 map.put("foo", value2).join();
372 assertTrue(false);
373 } catch (CompletionException e) {
374 assertEquals(ConcurrentModificationException.class, e.getCause().getClass());
375 }
376
Madan Jampani40f022e2016-03-02 21:35:14 -0800377 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800378
Madan Jampani74da78b2016-02-09 21:18:36 -0800379 map.commit(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800380 MapEvent<String, byte[]> event = listener.event();
381 assertNotNull(event);
382 assertEquals(MapEvent.Type.INSERT, event.type());
383 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800384
385 map.put("foo", value2).thenAccept(result -> {
386 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
387 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800388 event = listener.event();
389 assertNotNull(event);
390 assertEquals(MapEvent.Type.UPDATE, event.type());
391 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800392 }
393
394 protected void transactionRollbackTests(int clusterSize) throws Throwable {
395 createCopycatServers(clusterSize);
396 final byte[] value1 = Tools.getBytesUtf8("value1");
397 final byte[] value2 = Tools.getBytesUtf8("value2");
398
399 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
400 TestMapEventListener listener = new TestMapEventListener();
401
402 map.addListener(listener).join();
403
404 MapUpdate<String, byte[]> update1 =
405 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
406 .withKey("foo")
407 .withValue(value1)
408 .build();
Madan Jampani74da78b2016-02-09 21:18:36 -0800409 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampanicadd70b2016-02-08 13:45:43 -0800410 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800411 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800412 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800413 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800414
Madan Jampani74da78b2016-02-09 21:18:36 -0800415 map.rollback(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800416 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800417
418 map.get("foo").thenAccept(result -> {
419 assertNull(result);
420 }).join();
421
422 map.put("foo", value2).thenAccept(result -> {
423 assertNull(result);
424 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800425 MapEvent<String, byte[]> event = listener.event();
426 assertNotNull(event);
427 assertEquals(MapEvent.Type.INSERT, event.type());
428 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800429 }
430
431 private static class TestMapEventListener implements MapEventListener<String, byte[]> {
432
Madan Jampani40f022e2016-03-02 21:35:14 -0800433 private final BlockingQueue<MapEvent<String, byte[]>> queue = new ArrayBlockingQueue<>(1);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800434
435 @Override
436 public void event(MapEvent<String, byte[]> event) {
Madan Jampani40f022e2016-03-02 21:35:14 -0800437 try {
438 queue.put(event);
439 } catch (InterruptedException e) {
440 Throwables.propagate(e);
441 }
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800442 }
443
Madan Jampani40f022e2016-03-02 21:35:14 -0800444 public boolean eventReceived() {
445 return !queue.isEmpty();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800446 }
447
Madan Jampani40f022e2016-03-02 21:35:14 -0800448 public MapEvent<String, byte[]> event() throws InterruptedException {
449 return queue.take();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800450 }
451 }
452}