blob: df29b5e5cb0041310d8bcf08be526472c6b4a23b [file] [log] [blame]
Madan Jampani5e5b3d62016-02-01 16:03:33 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Madan Jampani5e5b3d62016-02-01 16:03:33 -08003 *
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
Aaron Kruglikovc0c27c02016-06-07 16:05:00 -070018import com.google.common.base.Throwables;
19import com.google.common.collect.Sets;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080020import io.atomix.resource.ResourceType;
Aaron Kruglikov5bb38122016-06-22 12:07:23 -070021import org.junit.Ignore;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080022import org.junit.Test;
23import org.onlab.util.Tools;
Madan Jampani74da78b2016-02-09 21:18:36 -080024import org.onosproject.store.primitives.MapUpdate;
Madan Jampanicadd70b2016-02-08 13:45:43 -080025import org.onosproject.store.primitives.TransactionId;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080026import org.onosproject.store.service.MapEvent;
27import org.onosproject.store.service.MapEventListener;
Madan Jampani74da78b2016-02-09 21:18:36 -080028import org.onosproject.store.service.MapTransaction;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080029import org.onosproject.store.service.Versioned;
30
Aaron Kruglikovc0c27c02016-06-07 16:05:00 -070031import java.util.Arrays;
32import java.util.ConcurrentModificationException;
33import java.util.List;
34import java.util.concurrent.ArrayBlockingQueue;
35import java.util.concurrent.BlockingQueue;
36import java.util.concurrent.CompletionException;
37import java.util.stream.Collectors;
38
39import static org.hamcrest.Matchers.is;
40import static org.junit.Assert.assertArrayEquals;
41import static org.junit.Assert.assertEquals;
42import static org.junit.Assert.assertFalse;
43import static org.junit.Assert.assertNotNull;
44import static org.junit.Assert.assertNull;
45import static org.junit.Assert.assertThat;
46import static org.junit.Assert.assertTrue;
47import static org.junit.Assert.fail;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080048
49/**
50 * Unit tests for {@link AtomixConsistentMap}.
51 */
Aaron Kruglikov5bb38122016-06-22 12:07:23 -070052@Ignore
Madan Jampani5e5b3d62016-02-01 16:03:33 -080053public class AtomixConsistentMapTest extends AtomixTestBase {
54
55 @Override
56 protected ResourceType resourceType() {
57 return new ResourceType(AtomixConsistentMap.class);
58 }
59
60 /**
61 * Tests various basic map operations.
62 */
63 @Test
64 public void testBasicMapOperations() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080065 basicMapOperationTests(3);
66 }
67
68 /**
69 * Tests various map compute* operations on different cluster sizes.
70 */
71 @Test
72 public void testMapComputeOperations() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080073 mapComputeOperationTests(3);
74 }
75
76 /**
77 * Tests map event notifications.
78 */
79 @Test
80 public void testMapListeners() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080081 mapListenerTests(3);
82 }
83
84 /**
85 * Tests map transaction commit.
86 */
87 @Test
88 public void testTransactionCommit() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080089 transactionCommitTests(3);
90 }
91
92 /**
93 * Tests map transaction rollback.
94 */
95 @Test
96 public void testTransactionRollback() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080097 transactionRollbackTests(3);
98 }
99
100 protected void basicMapOperationTests(int clusterSize) throws Throwable {
101 createCopycatServers(clusterSize);
102
103 final byte[] rawFooValue = Tools.getBytesUtf8("Hello foo!");
104 final byte[] rawBarValue = Tools.getBytesUtf8("Hello bar!");
105
Madan Jampani65f24bb2016-03-15 15:16:18 -0700106 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800107
108 map.isEmpty().thenAccept(result -> {
109 assertTrue(result);
110 }).join();
111
112 map.put("foo", rawFooValue).thenAccept(result -> {
113 assertNull(result);
114 }).join();
115
116 map.size().thenAccept(result -> {
117 assertTrue(result == 1);
118 }).join();
119
120 map.isEmpty().thenAccept(result -> {
121 assertFalse(result);
122 }).join();
123
124 map.putIfAbsent("foo", "Hello foo again!".getBytes()).thenAccept(result -> {
125 assertNotNull(result);
126 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
127 }).join();
128
129 map.putIfAbsent("bar", rawBarValue).thenAccept(result -> {
130 assertNull(result);
131 }).join();
132
133 map.size().thenAccept(result -> {
134 assertTrue(result == 2);
135 }).join();
136
137 map.keySet().thenAccept(result -> {
138 assertTrue(result.size() == 2);
139 assertTrue(result.containsAll(Sets.newHashSet("foo", "bar")));
140 }).join();
141
142 map.values().thenAccept(result -> {
143 assertTrue(result.size() == 2);
144 List<String> rawValues =
145 result.stream().map(v -> Tools.toStringUtf8(v.value())).collect(Collectors.toList());
146 assertTrue(rawValues.contains("Hello foo!"));
147 assertTrue(rawValues.contains("Hello bar!"));
148 }).join();
149
150 map.entrySet().thenAccept(result -> {
151 assertTrue(result.size() == 2);
152 // TODO: check entries
153 }).join();
154
155 map.get("foo").thenAccept(result -> {
156 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
157 }).join();
158
159 map.remove("foo").thenAccept(result -> {
160 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
161 }).join();
162
163 map.containsKey("foo").thenAccept(result -> {
164 assertFalse(result);
165 }).join();
166
167 map.get("foo").thenAccept(result -> {
168 assertNull(result);
169 }).join();
170
171 map.get("bar").thenAccept(result -> {
172 assertNotNull(result);
173 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
174 }).join();
175
176 map.containsKey("bar").thenAccept(result -> {
177 assertTrue(result);
178 }).join();
179
180 map.size().thenAccept(result -> {
181 assertTrue(result == 1);
182 }).join();
183
184 map.containsValue(rawBarValue).thenAccept(result -> {
185 assertTrue(result);
186 }).join();
187
188 map.containsValue(rawFooValue).thenAccept(result -> {
189 assertFalse(result);
190 }).join();
191
192 map.replace("bar", "Goodbye bar!".getBytes()).thenAccept(result -> {
193 assertNotNull(result);
194 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
195 }).join();
196
197 map.replace("foo", "Goodbye foo!".getBytes()).thenAccept(result -> {
198 assertNull(result);
199 }).join();
200
201 // try replace_if_value_match for a non-existent key
202 map.replace("foo", "Goodbye foo!".getBytes(), rawFooValue).thenAccept(result -> {
203 assertFalse(result);
204 }).join();
205
206 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
207 assertTrue(result);
208 }).join();
209
210 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
211 assertFalse(result);
212 }).join();
213
214 Versioned<byte[]> barValue = map.get("bar").join();
215 map.replace("bar", barValue.version(), "Goodbye bar!".getBytes()).thenAccept(result -> {
216 assertTrue(result);
217 }).join();
218
219 map.replace("bar", barValue.version(), rawBarValue).thenAccept(result -> {
220 assertFalse(result);
221 }).join();
222
223 map.clear().join();
224
225 map.size().thenAccept(result -> {
226 assertTrue(result == 0);
227 }).join();
228 }
229
230 public void mapComputeOperationTests(int clusterSize) throws Throwable {
231 createCopycatServers(clusterSize);
232 final byte[] value1 = Tools.getBytesUtf8("value1");
233 final byte[] value2 = Tools.getBytesUtf8("value2");
234 final byte[] value3 = Tools.getBytesUtf8("value3");
235
Madan Jampani65f24bb2016-03-15 15:16:18 -0700236 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800237
238 map.computeIfAbsent("foo", k -> value1).thenAccept(result -> {
239 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
240 }).join();
241
242 map.computeIfAbsent("foo", k -> value2).thenAccept(result -> {
243 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
244 }).join();
245
246 map.computeIfPresent("bar", (k, v) -> value2).thenAccept(result -> {
247 assertNull(result);
248 });
249
250 map.computeIfPresent("foo", (k, v) -> value3).thenAccept(result -> {
251 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value3));
252 }).join();
253
254 map.computeIfPresent("foo", (k, v) -> null).thenAccept(result -> {
255 assertNull(result);
256 }).join();
257
258 map.computeIf("foo", v -> v == null, (k, v) -> value1).thenAccept(result -> {
259 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
260 }).join();
261
262 map.compute("foo", (k, v) -> value2).thenAccept(result -> {
263 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value2));
264 }).join();
265 }
266
267
268 protected void mapListenerTests(int clusterSize) throws Throwable {
269 createCopycatServers(clusterSize);
270 final byte[] value1 = Tools.getBytesUtf8("value1");
271 final byte[] value2 = Tools.getBytesUtf8("value2");
272 final byte[] value3 = Tools.getBytesUtf8("value3");
273
Madan Jampani65f24bb2016-03-15 15:16:18 -0700274 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800275 TestMapEventListener listener = new TestMapEventListener();
276
277 // add listener; insert new value into map and verify an INSERT event is received.
Madan Jampani40f022e2016-03-02 21:35:14 -0800278 map.addListener(listener).thenCompose(v -> map.put("foo", value1)).join();
279 MapEvent<String, byte[]> event = listener.event();
280 assertNotNull(event);
281 assertEquals(MapEvent.Type.INSERT, event.type());
282 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800283
284 // remove listener and verify listener is not notified.
Madan Jampani40f022e2016-03-02 21:35:14 -0800285 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
286 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800287
288 // add the listener back and verify UPDATE events are received correctly
Madan Jampani40f022e2016-03-02 21:35:14 -0800289 map.addListener(listener).thenCompose(v -> map.put("foo", value3)).join();
290 event = listener.event();
291 assertNotNull(event);
292 assertEquals(MapEvent.Type.UPDATE, event.type());
293 assertTrue(Arrays.equals(value3, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800294
295 // perform a non-state changing operation and verify no events are received.
296 map.putIfAbsent("foo", value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800297 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800298
299 // verify REMOVE events are received correctly.
300 map.remove("foo").join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800301 event = listener.event();
302 assertNotNull(event);
303 assertEquals(MapEvent.Type.REMOVE, event.type());
304 assertTrue(Arrays.equals(value3, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800305
306 // verify compute methods also generate events.
307 map.computeIf("foo", v -> v == null, (k, v) -> value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800308 event = listener.event();
309 assertNotNull(event);
310 assertEquals(MapEvent.Type.INSERT, event.type());
311 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800312
313 map.compute("foo", (k, v) -> value2).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800314 event = listener.event();
315 assertNotNull(event);
316 assertEquals(MapEvent.Type.UPDATE, event.type());
317 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800318
319 map.computeIf("foo", v -> Arrays.equals(v, value2), (k, v) -> null).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800320 event = listener.event();
321 assertNotNull(event);
322 assertEquals(MapEvent.Type.REMOVE, event.type());
323 assertTrue(Arrays.equals(value2, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800324
325 map.removeListener(listener).join();
326 }
327
328 protected void transactionCommitTests(int clusterSize) throws Throwable {
329 createCopycatServers(clusterSize);
330 final byte[] value1 = Tools.getBytesUtf8("value1");
331 final byte[] value2 = Tools.getBytesUtf8("value2");
332
Madan Jampani65f24bb2016-03-15 15:16:18 -0700333 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800334 TestMapEventListener listener = new TestMapEventListener();
335
336 map.addListener(listener).join();
337
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700338 // PUT_IF_ABSENT
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800339 MapUpdate<String, byte[]> update1 =
340 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
341 .withKey("foo")
342 .withValue(value1)
343 .build();
344
Madan Jampani74da78b2016-02-09 21:18:36 -0800345 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800346
Madan Jampanicadd70b2016-02-08 13:45:43 -0800347 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800348 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800349 }).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700350 // verify changes in Tx is not visible yet until commit
Madan Jampani40f022e2016-03-02 21:35:14 -0800351 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800352
353 map.size().thenAccept(result -> {
354 assertTrue(result == 0);
355 }).join();
356
357 map.get("foo").thenAccept(result -> {
358 assertNull(result);
359 }).join();
360
361 try {
362 map.put("foo", value2).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700363 fail("update to map entry in open tx should fail with Exception");
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800364 } catch (CompletionException e) {
365 assertEquals(ConcurrentModificationException.class, e.getCause().getClass());
366 }
367
Madan Jampani40f022e2016-03-02 21:35:14 -0800368 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800369
Madan Jampani74da78b2016-02-09 21:18:36 -0800370 map.commit(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800371 MapEvent<String, byte[]> event = listener.event();
372 assertNotNull(event);
373 assertEquals(MapEvent.Type.INSERT, event.type());
374 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800375
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700376 // map should be update-able after commit
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800377 map.put("foo", value2).thenAccept(result -> {
378 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
379 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800380 event = listener.event();
381 assertNotNull(event);
382 assertEquals(MapEvent.Type.UPDATE, event.type());
383 assertTrue(Arrays.equals(value2, event.newValue().value()));
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700384
385
386 // REMOVE_IF_VERSION_MATCH
387 byte[] currFoo = map.get("foo").get().value();
388 long currFooVersion = map.get("foo").get().version();
389 MapUpdate<String, byte[]> remove1 =
390 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.REMOVE_IF_VERSION_MATCH)
391 .withKey("foo")
392 .withCurrentVersion(currFooVersion)
393 .build();
394
395 tx = new MapTransaction<>(TransactionId.from("tx2"), Arrays.asList(remove1));
396
397 map.prepare(tx).thenAccept(result -> {
398 assertTrue("prepare should succeed", result);
399 }).join();
400 // verify changes in Tx is not visible yet until commit
401 assertFalse(listener.eventReceived());
402
403 map.size().thenAccept(size -> {
404 assertThat(size, is(1));
405 }).join();
406
407 map.get("foo").thenAccept(result -> {
408 assertThat(result.value(), is(currFoo));
409 }).join();
410
411 map.commit(tx.transactionId()).join();
412 event = listener.event();
413 assertNotNull(event);
414 assertEquals(MapEvent.Type.REMOVE, event.type());
415 assertArrayEquals(currFoo, event.oldValue().value());
416
417 map.size().thenAccept(size -> {
418 assertThat(size, is(0));
419 }).join();
420
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800421 }
422
423 protected void transactionRollbackTests(int clusterSize) throws Throwable {
424 createCopycatServers(clusterSize);
425 final byte[] value1 = Tools.getBytesUtf8("value1");
426 final byte[] value2 = Tools.getBytesUtf8("value2");
427
Madan Jampani65f24bb2016-03-15 15:16:18 -0700428 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800429 TestMapEventListener listener = new TestMapEventListener();
430
431 map.addListener(listener).join();
432
433 MapUpdate<String, byte[]> update1 =
434 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
435 .withKey("foo")
436 .withValue(value1)
437 .build();
Madan Jampani74da78b2016-02-09 21:18:36 -0800438 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampanicadd70b2016-02-08 13:45:43 -0800439 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800440 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800441 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800442 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800443
Madan Jampani74da78b2016-02-09 21:18:36 -0800444 map.rollback(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800445 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800446
447 map.get("foo").thenAccept(result -> {
448 assertNull(result);
449 }).join();
450
451 map.put("foo", value2).thenAccept(result -> {
452 assertNull(result);
453 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800454 MapEvent<String, byte[]> event = listener.event();
455 assertNotNull(event);
456 assertEquals(MapEvent.Type.INSERT, event.type());
457 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800458 }
459
460 private static class TestMapEventListener implements MapEventListener<String, byte[]> {
461
Madan Jampani40f022e2016-03-02 21:35:14 -0800462 private final BlockingQueue<MapEvent<String, byte[]>> queue = new ArrayBlockingQueue<>(1);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800463
464 @Override
465 public void event(MapEvent<String, byte[]> event) {
Madan Jampani40f022e2016-03-02 21:35:14 -0800466 try {
467 queue.put(event);
468 } catch (InterruptedException e) {
469 Throwables.propagate(e);
470 }
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800471 }
472
Madan Jampani40f022e2016-03-02 21:35:14 -0800473 public boolean eventReceived() {
474 return !queue.isEmpty();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800475 }
476
Madan Jampani40f022e2016-03-02 21:35:14 -0800477 public MapEvent<String, byte[]> event() throws InterruptedException {
478 return queue.take();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800479 }
480 }
481}