blob: a5fdb4948a6eeac8e5590067c80438394a6bf523 [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
18import io.atomix.resource.ResourceType;
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -070019
20import static org.hamcrest.Matchers.*;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080021import static org.junit.Assert.*;
22
23import java.util.Arrays;
24import java.util.ConcurrentModificationException;
25import java.util.List;
Madan Jampani40f022e2016-03-02 21:35:14 -080026import java.util.concurrent.ArrayBlockingQueue;
27import java.util.concurrent.BlockingQueue;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080028import java.util.concurrent.CompletionException;
29import java.util.stream.Collectors;
30
Madan Jampani07b54482016-03-09 10:34:18 -080031import org.junit.Ignore;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080032import org.junit.Test;
33import org.onlab.util.Tools;
Madan Jampani74da78b2016-02-09 21:18:36 -080034import org.onosproject.store.primitives.MapUpdate;
Madan Jampanicadd70b2016-02-08 13:45:43 -080035import org.onosproject.store.primitives.TransactionId;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080036import org.onosproject.store.service.MapEvent;
37import org.onosproject.store.service.MapEventListener;
Madan Jampani74da78b2016-02-09 21:18:36 -080038import org.onosproject.store.service.MapTransaction;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080039import org.onosproject.store.service.Versioned;
40
Madan Jampani40f022e2016-03-02 21:35:14 -080041import com.google.common.base.Throwables;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080042import com.google.common.collect.Sets;
43
44/**
45 * Unit tests for {@link AtomixConsistentMap}.
46 */
Madan Jampani07b54482016-03-09 10:34:18 -080047@Ignore
Madan Jampani5e5b3d62016-02-01 16:03:33 -080048public class AtomixConsistentMapTest extends AtomixTestBase {
49
50 @Override
51 protected ResourceType resourceType() {
52 return new ResourceType(AtomixConsistentMap.class);
53 }
54
55 /**
56 * Tests various basic map operations.
57 */
58 @Test
59 public void testBasicMapOperations() throws Throwable {
60 basicMapOperationTests(1);
61 clearTests();
62 basicMapOperationTests(2);
63 clearTests();
64 basicMapOperationTests(3);
65 }
66
67 /**
68 * Tests various map compute* operations on different cluster sizes.
69 */
70 @Test
71 public void testMapComputeOperations() throws Throwable {
72 mapComputeOperationTests(1);
73 clearTests();
74 mapComputeOperationTests(2);
75 clearTests();
76 mapComputeOperationTests(3);
77 }
78
79 /**
80 * Tests map event notifications.
81 */
82 @Test
83 public void testMapListeners() throws Throwable {
84 mapListenerTests(1);
85 clearTests();
86 mapListenerTests(2);
87 clearTests();
88 mapListenerTests(3);
89 }
90
91 /**
92 * Tests map transaction commit.
93 */
94 @Test
95 public void testTransactionCommit() throws Throwable {
96 transactionCommitTests(1);
97 clearTests();
98 transactionCommitTests(2);
99 clearTests();
100 transactionCommitTests(3);
101 }
102
103 /**
104 * Tests map transaction rollback.
105 */
106 @Test
107 public void testTransactionRollback() throws Throwable {
108 transactionRollbackTests(1);
109 clearTests();
110 transactionRollbackTests(2);
111 clearTests();
112 transactionRollbackTests(3);
113 }
114
115 protected void basicMapOperationTests(int clusterSize) throws Throwable {
116 createCopycatServers(clusterSize);
117
118 final byte[] rawFooValue = Tools.getBytesUtf8("Hello foo!");
119 final byte[] rawBarValue = Tools.getBytesUtf8("Hello bar!");
120
Madan Jampani65f24bb2016-03-15 15:16:18 -0700121 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800122
123 map.isEmpty().thenAccept(result -> {
124 assertTrue(result);
125 }).join();
126
127 map.put("foo", rawFooValue).thenAccept(result -> {
128 assertNull(result);
129 }).join();
130
131 map.size().thenAccept(result -> {
132 assertTrue(result == 1);
133 }).join();
134
135 map.isEmpty().thenAccept(result -> {
136 assertFalse(result);
137 }).join();
138
139 map.putIfAbsent("foo", "Hello foo again!".getBytes()).thenAccept(result -> {
140 assertNotNull(result);
141 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
142 }).join();
143
144 map.putIfAbsent("bar", rawBarValue).thenAccept(result -> {
145 assertNull(result);
146 }).join();
147
148 map.size().thenAccept(result -> {
149 assertTrue(result == 2);
150 }).join();
151
152 map.keySet().thenAccept(result -> {
153 assertTrue(result.size() == 2);
154 assertTrue(result.containsAll(Sets.newHashSet("foo", "bar")));
155 }).join();
156
157 map.values().thenAccept(result -> {
158 assertTrue(result.size() == 2);
159 List<String> rawValues =
160 result.stream().map(v -> Tools.toStringUtf8(v.value())).collect(Collectors.toList());
161 assertTrue(rawValues.contains("Hello foo!"));
162 assertTrue(rawValues.contains("Hello bar!"));
163 }).join();
164
165 map.entrySet().thenAccept(result -> {
166 assertTrue(result.size() == 2);
167 // TODO: check entries
168 }).join();
169
170 map.get("foo").thenAccept(result -> {
171 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
172 }).join();
173
174 map.remove("foo").thenAccept(result -> {
175 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
176 }).join();
177
178 map.containsKey("foo").thenAccept(result -> {
179 assertFalse(result);
180 }).join();
181
182 map.get("foo").thenAccept(result -> {
183 assertNull(result);
184 }).join();
185
186 map.get("bar").thenAccept(result -> {
187 assertNotNull(result);
188 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
189 }).join();
190
191 map.containsKey("bar").thenAccept(result -> {
192 assertTrue(result);
193 }).join();
194
195 map.size().thenAccept(result -> {
196 assertTrue(result == 1);
197 }).join();
198
199 map.containsValue(rawBarValue).thenAccept(result -> {
200 assertTrue(result);
201 }).join();
202
203 map.containsValue(rawFooValue).thenAccept(result -> {
204 assertFalse(result);
205 }).join();
206
207 map.replace("bar", "Goodbye bar!".getBytes()).thenAccept(result -> {
208 assertNotNull(result);
209 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
210 }).join();
211
212 map.replace("foo", "Goodbye foo!".getBytes()).thenAccept(result -> {
213 assertNull(result);
214 }).join();
215
216 // try replace_if_value_match for a non-existent key
217 map.replace("foo", "Goodbye foo!".getBytes(), rawFooValue).thenAccept(result -> {
218 assertFalse(result);
219 }).join();
220
221 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
222 assertTrue(result);
223 }).join();
224
225 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
226 assertFalse(result);
227 }).join();
228
229 Versioned<byte[]> barValue = map.get("bar").join();
230 map.replace("bar", barValue.version(), "Goodbye bar!".getBytes()).thenAccept(result -> {
231 assertTrue(result);
232 }).join();
233
234 map.replace("bar", barValue.version(), rawBarValue).thenAccept(result -> {
235 assertFalse(result);
236 }).join();
237
238 map.clear().join();
239
240 map.size().thenAccept(result -> {
241 assertTrue(result == 0);
242 }).join();
243 }
244
245 public void mapComputeOperationTests(int clusterSize) throws Throwable {
246 createCopycatServers(clusterSize);
247 final byte[] value1 = Tools.getBytesUtf8("value1");
248 final byte[] value2 = Tools.getBytesUtf8("value2");
249 final byte[] value3 = Tools.getBytesUtf8("value3");
250
Madan Jampani65f24bb2016-03-15 15:16:18 -0700251 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800252
253 map.computeIfAbsent("foo", k -> value1).thenAccept(result -> {
254 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
255 }).join();
256
257 map.computeIfAbsent("foo", k -> value2).thenAccept(result -> {
258 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
259 }).join();
260
261 map.computeIfPresent("bar", (k, v) -> value2).thenAccept(result -> {
262 assertNull(result);
263 });
264
265 map.computeIfPresent("foo", (k, v) -> value3).thenAccept(result -> {
266 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value3));
267 }).join();
268
269 map.computeIfPresent("foo", (k, v) -> null).thenAccept(result -> {
270 assertNull(result);
271 }).join();
272
273 map.computeIf("foo", v -> v == null, (k, v) -> value1).thenAccept(result -> {
274 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
275 }).join();
276
277 map.compute("foo", (k, v) -> value2).thenAccept(result -> {
278 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value2));
279 }).join();
280 }
281
282
283 protected void mapListenerTests(int clusterSize) throws Throwable {
284 createCopycatServers(clusterSize);
285 final byte[] value1 = Tools.getBytesUtf8("value1");
286 final byte[] value2 = Tools.getBytesUtf8("value2");
287 final byte[] value3 = Tools.getBytesUtf8("value3");
288
Madan Jampani65f24bb2016-03-15 15:16:18 -0700289 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800290 TestMapEventListener listener = new TestMapEventListener();
291
292 // add listener; insert new value into map and verify an INSERT event is received.
Madan Jampani40f022e2016-03-02 21:35:14 -0800293 map.addListener(listener).thenCompose(v -> map.put("foo", value1)).join();
294 MapEvent<String, byte[]> event = listener.event();
295 assertNotNull(event);
296 assertEquals(MapEvent.Type.INSERT, event.type());
297 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800298
299 // remove listener and verify listener is not notified.
Madan Jampani40f022e2016-03-02 21:35:14 -0800300 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
301 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800302
303 // add the listener back and verify UPDATE events are received correctly
Madan Jampani40f022e2016-03-02 21:35:14 -0800304 map.addListener(listener).thenCompose(v -> map.put("foo", value3)).join();
305 event = listener.event();
306 assertNotNull(event);
307 assertEquals(MapEvent.Type.UPDATE, event.type());
308 assertTrue(Arrays.equals(value3, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800309
310 // perform a non-state changing operation and verify no events are received.
311 map.putIfAbsent("foo", value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800312 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800313
314 // verify REMOVE events are received correctly.
315 map.remove("foo").join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800316 event = listener.event();
317 assertNotNull(event);
318 assertEquals(MapEvent.Type.REMOVE, event.type());
319 assertTrue(Arrays.equals(value3, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800320
321 // verify compute methods also generate events.
322 map.computeIf("foo", v -> v == null, (k, v) -> value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800323 event = listener.event();
324 assertNotNull(event);
325 assertEquals(MapEvent.Type.INSERT, event.type());
326 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800327
328 map.compute("foo", (k, v) -> value2).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800329 event = listener.event();
330 assertNotNull(event);
331 assertEquals(MapEvent.Type.UPDATE, event.type());
332 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800333
334 map.computeIf("foo", v -> Arrays.equals(v, value2), (k, v) -> null).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800335 event = listener.event();
336 assertNotNull(event);
337 assertEquals(MapEvent.Type.REMOVE, event.type());
338 assertTrue(Arrays.equals(value2, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800339
340 map.removeListener(listener).join();
341 }
342
343 protected void transactionCommitTests(int clusterSize) throws Throwable {
344 createCopycatServers(clusterSize);
345 final byte[] value1 = Tools.getBytesUtf8("value1");
346 final byte[] value2 = Tools.getBytesUtf8("value2");
347
Madan Jampani65f24bb2016-03-15 15:16:18 -0700348 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800349 TestMapEventListener listener = new TestMapEventListener();
350
351 map.addListener(listener).join();
352
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700353 // PUT_IF_ABSENT
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800354 MapUpdate<String, byte[]> update1 =
355 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
356 .withKey("foo")
357 .withValue(value1)
358 .build();
359
Madan Jampani74da78b2016-02-09 21:18:36 -0800360 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800361
Madan Jampanicadd70b2016-02-08 13:45:43 -0800362 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800363 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800364 }).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700365 // verify changes in Tx is not visible yet until commit
Madan Jampani40f022e2016-03-02 21:35:14 -0800366 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800367
368 map.size().thenAccept(result -> {
369 assertTrue(result == 0);
370 }).join();
371
372 map.get("foo").thenAccept(result -> {
373 assertNull(result);
374 }).join();
375
376 try {
377 map.put("foo", value2).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700378 fail("update to map entry in open tx should fail with Exception");
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800379 } catch (CompletionException e) {
380 assertEquals(ConcurrentModificationException.class, e.getCause().getClass());
381 }
382
Madan Jampani40f022e2016-03-02 21:35:14 -0800383 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800384
Madan Jampani74da78b2016-02-09 21:18:36 -0800385 map.commit(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800386 MapEvent<String, byte[]> event = listener.event();
387 assertNotNull(event);
388 assertEquals(MapEvent.Type.INSERT, event.type());
389 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800390
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700391 // map should be update-able after commit
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800392 map.put("foo", value2).thenAccept(result -> {
393 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
394 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800395 event = listener.event();
396 assertNotNull(event);
397 assertEquals(MapEvent.Type.UPDATE, event.type());
398 assertTrue(Arrays.equals(value2, event.newValue().value()));
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700399
400
401 // REMOVE_IF_VERSION_MATCH
402 byte[] currFoo = map.get("foo").get().value();
403 long currFooVersion = map.get("foo").get().version();
404 MapUpdate<String, byte[]> remove1 =
405 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.REMOVE_IF_VERSION_MATCH)
406 .withKey("foo")
407 .withCurrentVersion(currFooVersion)
408 .build();
409
410 tx = new MapTransaction<>(TransactionId.from("tx2"), Arrays.asList(remove1));
411
412 map.prepare(tx).thenAccept(result -> {
413 assertTrue("prepare should succeed", result);
414 }).join();
415 // verify changes in Tx is not visible yet until commit
416 assertFalse(listener.eventReceived());
417
418 map.size().thenAccept(size -> {
419 assertThat(size, is(1));
420 }).join();
421
422 map.get("foo").thenAccept(result -> {
423 assertThat(result.value(), is(currFoo));
424 }).join();
425
426 map.commit(tx.transactionId()).join();
427 event = listener.event();
428 assertNotNull(event);
429 assertEquals(MapEvent.Type.REMOVE, event.type());
430 assertArrayEquals(currFoo, event.oldValue().value());
431
432 map.size().thenAccept(size -> {
433 assertThat(size, is(0));
434 }).join();
435
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800436 }
437
438 protected void transactionRollbackTests(int clusterSize) throws Throwable {
439 createCopycatServers(clusterSize);
440 final byte[] value1 = Tools.getBytesUtf8("value1");
441 final byte[] value2 = Tools.getBytesUtf8("value2");
442
Madan Jampani65f24bb2016-03-15 15:16:18 -0700443 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800444 TestMapEventListener listener = new TestMapEventListener();
445
446 map.addListener(listener).join();
447
448 MapUpdate<String, byte[]> update1 =
449 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
450 .withKey("foo")
451 .withValue(value1)
452 .build();
Madan Jampani74da78b2016-02-09 21:18:36 -0800453 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampanicadd70b2016-02-08 13:45:43 -0800454 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800455 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800456 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800457 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800458
Madan Jampani74da78b2016-02-09 21:18:36 -0800459 map.rollback(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800460 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800461
462 map.get("foo").thenAccept(result -> {
463 assertNull(result);
464 }).join();
465
466 map.put("foo", value2).thenAccept(result -> {
467 assertNull(result);
468 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800469 MapEvent<String, byte[]> event = listener.event();
470 assertNotNull(event);
471 assertEquals(MapEvent.Type.INSERT, event.type());
472 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800473 }
474
475 private static class TestMapEventListener implements MapEventListener<String, byte[]> {
476
Madan Jampani40f022e2016-03-02 21:35:14 -0800477 private final BlockingQueue<MapEvent<String, byte[]>> queue = new ArrayBlockingQueue<>(1);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800478
479 @Override
480 public void event(MapEvent<String, byte[]> event) {
Madan Jampani40f022e2016-03-02 21:35:14 -0800481 try {
482 queue.put(event);
483 } catch (InterruptedException e) {
484 Throwables.propagate(e);
485 }
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800486 }
487
Madan Jampani40f022e2016-03-02 21:35:14 -0800488 public boolean eventReceived() {
489 return !queue.isEmpty();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800490 }
491
Madan Jampani40f022e2016-03-02 21:35:14 -0800492 public MapEvent<String, byte[]> event() throws InterruptedException {
493 return queue.take();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800494 }
495 }
496}