blob: df44ecf335c4d82ca1d92cad8bd809342480812e [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;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080021import org.junit.Test;
22import org.onlab.util.Tools;
Madan Jampani74da78b2016-02-09 21:18:36 -080023import org.onosproject.store.primitives.MapUpdate;
Madan Jampanicadd70b2016-02-08 13:45:43 -080024import org.onosproject.store.primitives.TransactionId;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080025import org.onosproject.store.service.MapEvent;
26import org.onosproject.store.service.MapEventListener;
Madan Jampani74da78b2016-02-09 21:18:36 -080027import org.onosproject.store.service.MapTransaction;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080028import org.onosproject.store.service.Versioned;
29
Aaron Kruglikovc0c27c02016-06-07 16:05:00 -070030import java.util.Arrays;
31import java.util.ConcurrentModificationException;
32import java.util.List;
33import java.util.concurrent.ArrayBlockingQueue;
34import java.util.concurrent.BlockingQueue;
35import java.util.concurrent.CompletionException;
36import java.util.stream.Collectors;
37
38import static org.hamcrest.Matchers.is;
39import static org.junit.Assert.assertArrayEquals;
40import static org.junit.Assert.assertEquals;
41import static org.junit.Assert.assertFalse;
42import static org.junit.Assert.assertNotNull;
43import static org.junit.Assert.assertNull;
44import static org.junit.Assert.assertThat;
45import static org.junit.Assert.assertTrue;
46import static org.junit.Assert.fail;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080047
48/**
49 * Unit tests for {@link AtomixConsistentMap}.
50 */
Madan Jampani5e5b3d62016-02-01 16:03:33 -080051public class AtomixConsistentMapTest extends AtomixTestBase {
52
53 @Override
54 protected ResourceType resourceType() {
55 return new ResourceType(AtomixConsistentMap.class);
56 }
57
58 /**
59 * Tests various basic map operations.
60 */
61 @Test
62 public void testBasicMapOperations() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080063 basicMapOperationTests(3);
64 }
65
66 /**
67 * Tests various map compute* operations on different cluster sizes.
68 */
69 @Test
70 public void testMapComputeOperations() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080071 mapComputeOperationTests(3);
72 }
73
74 /**
75 * Tests map event notifications.
76 */
77 @Test
78 public void testMapListeners() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080079 mapListenerTests(3);
80 }
81
82 /**
83 * Tests map transaction commit.
84 */
85 @Test
86 public void testTransactionCommit() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080087 transactionCommitTests(3);
88 }
89
90 /**
91 * Tests map transaction rollback.
92 */
93 @Test
94 public void testTransactionRollback() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080095 transactionRollbackTests(3);
96 }
97
98 protected void basicMapOperationTests(int clusterSize) throws Throwable {
99 createCopycatServers(clusterSize);
100
101 final byte[] rawFooValue = Tools.getBytesUtf8("Hello foo!");
102 final byte[] rawBarValue = Tools.getBytesUtf8("Hello bar!");
103
Madan Jampani65f24bb2016-03-15 15:16:18 -0700104 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800105
106 map.isEmpty().thenAccept(result -> {
107 assertTrue(result);
108 }).join();
109
110 map.put("foo", rawFooValue).thenAccept(result -> {
111 assertNull(result);
112 }).join();
113
114 map.size().thenAccept(result -> {
115 assertTrue(result == 1);
116 }).join();
117
118 map.isEmpty().thenAccept(result -> {
119 assertFalse(result);
120 }).join();
121
122 map.putIfAbsent("foo", "Hello foo again!".getBytes()).thenAccept(result -> {
123 assertNotNull(result);
124 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
125 }).join();
126
127 map.putIfAbsent("bar", rawBarValue).thenAccept(result -> {
128 assertNull(result);
129 }).join();
130
131 map.size().thenAccept(result -> {
132 assertTrue(result == 2);
133 }).join();
134
135 map.keySet().thenAccept(result -> {
136 assertTrue(result.size() == 2);
137 assertTrue(result.containsAll(Sets.newHashSet("foo", "bar")));
138 }).join();
139
140 map.values().thenAccept(result -> {
141 assertTrue(result.size() == 2);
142 List<String> rawValues =
143 result.stream().map(v -> Tools.toStringUtf8(v.value())).collect(Collectors.toList());
144 assertTrue(rawValues.contains("Hello foo!"));
145 assertTrue(rawValues.contains("Hello bar!"));
146 }).join();
147
148 map.entrySet().thenAccept(result -> {
149 assertTrue(result.size() == 2);
150 // TODO: check entries
151 }).join();
152
153 map.get("foo").thenAccept(result -> {
154 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
155 }).join();
156
157 map.remove("foo").thenAccept(result -> {
158 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
159 }).join();
160
161 map.containsKey("foo").thenAccept(result -> {
162 assertFalse(result);
163 }).join();
164
165 map.get("foo").thenAccept(result -> {
166 assertNull(result);
167 }).join();
168
169 map.get("bar").thenAccept(result -> {
170 assertNotNull(result);
171 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
172 }).join();
173
174 map.containsKey("bar").thenAccept(result -> {
175 assertTrue(result);
176 }).join();
177
178 map.size().thenAccept(result -> {
179 assertTrue(result == 1);
180 }).join();
181
182 map.containsValue(rawBarValue).thenAccept(result -> {
183 assertTrue(result);
184 }).join();
185
186 map.containsValue(rawFooValue).thenAccept(result -> {
187 assertFalse(result);
188 }).join();
189
190 map.replace("bar", "Goodbye bar!".getBytes()).thenAccept(result -> {
191 assertNotNull(result);
192 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
193 }).join();
194
195 map.replace("foo", "Goodbye foo!".getBytes()).thenAccept(result -> {
196 assertNull(result);
197 }).join();
198
199 // try replace_if_value_match for a non-existent key
200 map.replace("foo", "Goodbye foo!".getBytes(), rawFooValue).thenAccept(result -> {
201 assertFalse(result);
202 }).join();
203
204 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
205 assertTrue(result);
206 }).join();
207
208 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
209 assertFalse(result);
210 }).join();
211
212 Versioned<byte[]> barValue = map.get("bar").join();
213 map.replace("bar", barValue.version(), "Goodbye bar!".getBytes()).thenAccept(result -> {
214 assertTrue(result);
215 }).join();
216
217 map.replace("bar", barValue.version(), rawBarValue).thenAccept(result -> {
218 assertFalse(result);
219 }).join();
220
221 map.clear().join();
222
223 map.size().thenAccept(result -> {
224 assertTrue(result == 0);
225 }).join();
226 }
227
228 public void mapComputeOperationTests(int clusterSize) throws Throwable {
229 createCopycatServers(clusterSize);
230 final byte[] value1 = Tools.getBytesUtf8("value1");
231 final byte[] value2 = Tools.getBytesUtf8("value2");
232 final byte[] value3 = Tools.getBytesUtf8("value3");
233
Madan Jampani65f24bb2016-03-15 15:16:18 -0700234 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800235
236 map.computeIfAbsent("foo", k -> value1).thenAccept(result -> {
237 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
238 }).join();
239
240 map.computeIfAbsent("foo", k -> value2).thenAccept(result -> {
241 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
242 }).join();
243
244 map.computeIfPresent("bar", (k, v) -> value2).thenAccept(result -> {
245 assertNull(result);
246 });
247
248 map.computeIfPresent("foo", (k, v) -> value3).thenAccept(result -> {
249 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value3));
250 }).join();
251
252 map.computeIfPresent("foo", (k, v) -> null).thenAccept(result -> {
253 assertNull(result);
254 }).join();
255
256 map.computeIf("foo", v -> v == null, (k, v) -> value1).thenAccept(result -> {
257 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
258 }).join();
259
260 map.compute("foo", (k, v) -> value2).thenAccept(result -> {
261 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value2));
262 }).join();
263 }
264
265
266 protected void mapListenerTests(int clusterSize) throws Throwable {
267 createCopycatServers(clusterSize);
268 final byte[] value1 = Tools.getBytesUtf8("value1");
269 final byte[] value2 = Tools.getBytesUtf8("value2");
270 final byte[] value3 = Tools.getBytesUtf8("value3");
271
Madan Jampani65f24bb2016-03-15 15:16:18 -0700272 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800273 TestMapEventListener listener = new TestMapEventListener();
274
275 // add listener; insert new value into map and verify an INSERT event is received.
Madan Jampani40f022e2016-03-02 21:35:14 -0800276 map.addListener(listener).thenCompose(v -> map.put("foo", value1)).join();
277 MapEvent<String, byte[]> event = listener.event();
278 assertNotNull(event);
279 assertEquals(MapEvent.Type.INSERT, event.type());
280 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800281
282 // remove listener and verify listener is not notified.
Madan Jampani40f022e2016-03-02 21:35:14 -0800283 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
284 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800285
286 // add the listener back and verify UPDATE events are received correctly
Madan Jampani40f022e2016-03-02 21:35:14 -0800287 map.addListener(listener).thenCompose(v -> map.put("foo", value3)).join();
288 event = listener.event();
289 assertNotNull(event);
290 assertEquals(MapEvent.Type.UPDATE, event.type());
291 assertTrue(Arrays.equals(value3, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800292
293 // perform a non-state changing operation and verify no events are received.
294 map.putIfAbsent("foo", value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800295 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800296
297 // verify REMOVE events are received correctly.
298 map.remove("foo").join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800299 event = listener.event();
300 assertNotNull(event);
301 assertEquals(MapEvent.Type.REMOVE, event.type());
302 assertTrue(Arrays.equals(value3, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800303
304 // verify compute methods also generate events.
305 map.computeIf("foo", v -> v == null, (k, v) -> value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800306 event = listener.event();
307 assertNotNull(event);
308 assertEquals(MapEvent.Type.INSERT, event.type());
309 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800310
311 map.compute("foo", (k, v) -> value2).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800312 event = listener.event();
313 assertNotNull(event);
314 assertEquals(MapEvent.Type.UPDATE, event.type());
315 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800316
317 map.computeIf("foo", v -> Arrays.equals(v, value2), (k, v) -> null).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800318 event = listener.event();
319 assertNotNull(event);
320 assertEquals(MapEvent.Type.REMOVE, event.type());
321 assertTrue(Arrays.equals(value2, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800322
323 map.removeListener(listener).join();
324 }
325
326 protected void transactionCommitTests(int clusterSize) throws Throwable {
327 createCopycatServers(clusterSize);
328 final byte[] value1 = Tools.getBytesUtf8("value1");
329 final byte[] value2 = Tools.getBytesUtf8("value2");
330
Madan Jampani65f24bb2016-03-15 15:16:18 -0700331 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800332 TestMapEventListener listener = new TestMapEventListener();
333
334 map.addListener(listener).join();
335
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700336 // PUT_IF_ABSENT
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800337 MapUpdate<String, byte[]> update1 =
338 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
339 .withKey("foo")
340 .withValue(value1)
341 .build();
342
Madan Jampani74da78b2016-02-09 21:18:36 -0800343 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800344
Madan Jampanicadd70b2016-02-08 13:45:43 -0800345 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800346 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800347 }).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700348 // verify changes in Tx is not visible yet until commit
Madan Jampani40f022e2016-03-02 21:35:14 -0800349 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800350
351 map.size().thenAccept(result -> {
352 assertTrue(result == 0);
353 }).join();
354
355 map.get("foo").thenAccept(result -> {
356 assertNull(result);
357 }).join();
358
359 try {
360 map.put("foo", value2).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700361 fail("update to map entry in open tx should fail with Exception");
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800362 } catch (CompletionException e) {
363 assertEquals(ConcurrentModificationException.class, e.getCause().getClass());
364 }
365
Madan Jampani40f022e2016-03-02 21:35:14 -0800366 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800367
Madan Jampani74da78b2016-02-09 21:18:36 -0800368 map.commit(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800369 MapEvent<String, byte[]> event = listener.event();
370 assertNotNull(event);
371 assertEquals(MapEvent.Type.INSERT, event.type());
372 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800373
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700374 // map should be update-able after commit
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800375 map.put("foo", value2).thenAccept(result -> {
376 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
377 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800378 event = listener.event();
379 assertNotNull(event);
380 assertEquals(MapEvent.Type.UPDATE, event.type());
381 assertTrue(Arrays.equals(value2, event.newValue().value()));
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700382
383
384 // REMOVE_IF_VERSION_MATCH
385 byte[] currFoo = map.get("foo").get().value();
386 long currFooVersion = map.get("foo").get().version();
387 MapUpdate<String, byte[]> remove1 =
388 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.REMOVE_IF_VERSION_MATCH)
389 .withKey("foo")
390 .withCurrentVersion(currFooVersion)
391 .build();
392
393 tx = new MapTransaction<>(TransactionId.from("tx2"), Arrays.asList(remove1));
394
395 map.prepare(tx).thenAccept(result -> {
396 assertTrue("prepare should succeed", result);
397 }).join();
398 // verify changes in Tx is not visible yet until commit
399 assertFalse(listener.eventReceived());
400
401 map.size().thenAccept(size -> {
402 assertThat(size, is(1));
403 }).join();
404
405 map.get("foo").thenAccept(result -> {
406 assertThat(result.value(), is(currFoo));
407 }).join();
408
409 map.commit(tx.transactionId()).join();
410 event = listener.event();
411 assertNotNull(event);
412 assertEquals(MapEvent.Type.REMOVE, event.type());
413 assertArrayEquals(currFoo, event.oldValue().value());
414
415 map.size().thenAccept(size -> {
416 assertThat(size, is(0));
417 }).join();
418
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800419 }
420
421 protected void transactionRollbackTests(int clusterSize) throws Throwable {
422 createCopycatServers(clusterSize);
423 final byte[] value1 = Tools.getBytesUtf8("value1");
424 final byte[] value2 = Tools.getBytesUtf8("value2");
425
Madan Jampani65f24bb2016-03-15 15:16:18 -0700426 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800427 TestMapEventListener listener = new TestMapEventListener();
428
429 map.addListener(listener).join();
430
431 MapUpdate<String, byte[]> update1 =
432 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
433 .withKey("foo")
434 .withValue(value1)
435 .build();
Madan Jampani74da78b2016-02-09 21:18:36 -0800436 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampanicadd70b2016-02-08 13:45:43 -0800437 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800438 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800439 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800440 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800441
Madan Jampani74da78b2016-02-09 21:18:36 -0800442 map.rollback(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800443 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800444
445 map.get("foo").thenAccept(result -> {
446 assertNull(result);
447 }).join();
448
449 map.put("foo", value2).thenAccept(result -> {
450 assertNull(result);
451 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800452 MapEvent<String, byte[]> event = listener.event();
453 assertNotNull(event);
454 assertEquals(MapEvent.Type.INSERT, event.type());
455 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800456 }
457
458 private static class TestMapEventListener implements MapEventListener<String, byte[]> {
459
Madan Jampani40f022e2016-03-02 21:35:14 -0800460 private final BlockingQueue<MapEvent<String, byte[]>> queue = new ArrayBlockingQueue<>(1);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800461
462 @Override
463 public void event(MapEvent<String, byte[]> event) {
Madan Jampani40f022e2016-03-02 21:35:14 -0800464 try {
465 queue.put(event);
466 } catch (InterruptedException e) {
467 Throwables.propagate(e);
468 }
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800469 }
470
Madan Jampani40f022e2016-03-02 21:35:14 -0800471 public boolean eventReceived() {
472 return !queue.isEmpty();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800473 }
474
Madan Jampani40f022e2016-03-02 21:35:14 -0800475 public MapEvent<String, byte[]> event() throws InterruptedException {
476 return queue.take();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800477 }
478 }
479}