blob: f1a3c2fc406348044d8caaf802fc1bfd65eb3239 [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 Kruglikovb5a41e52016-06-23 15:37:41 -070021import org.junit.AfterClass;
22import org.junit.BeforeClass;
Aaron Kruglikov43d843d2016-07-01 06:39:38 -070023import org.junit.Ignore;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080024import org.junit.Test;
25import org.onlab.util.Tools;
Madan Jampani74da78b2016-02-09 21:18:36 -080026import org.onosproject.store.primitives.MapUpdate;
Madan Jampanicadd70b2016-02-08 13:45:43 -080027import org.onosproject.store.primitives.TransactionId;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080028import org.onosproject.store.service.MapEvent;
29import org.onosproject.store.service.MapEventListener;
Madan Jampani74da78b2016-02-09 21:18:36 -080030import org.onosproject.store.service.MapTransaction;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080031import org.onosproject.store.service.Versioned;
32
Aaron Kruglikovc0c27c02016-06-07 16:05:00 -070033import java.util.Arrays;
34import java.util.ConcurrentModificationException;
35import java.util.List;
36import java.util.concurrent.ArrayBlockingQueue;
37import java.util.concurrent.BlockingQueue;
38import java.util.concurrent.CompletionException;
39import java.util.stream.Collectors;
40
41import static org.hamcrest.Matchers.is;
42import static org.junit.Assert.assertArrayEquals;
43import static org.junit.Assert.assertEquals;
44import static org.junit.Assert.assertFalse;
45import static org.junit.Assert.assertNotNull;
46import static org.junit.Assert.assertNull;
47import static org.junit.Assert.assertThat;
48import static org.junit.Assert.assertTrue;
49import static org.junit.Assert.fail;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080050
51/**
52 * Unit tests for {@link AtomixConsistentMap}.
53 */
Aaron Kruglikov43d843d2016-07-01 06:39:38 -070054@Ignore
Madan Jampani5e5b3d62016-02-01 16:03:33 -080055public class AtomixConsistentMapTest extends AtomixTestBase {
56
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070057 @BeforeClass
58 public static void preTestSetup() throws Throwable {
59 createCopycatServers(3);
60 }
61
62 @AfterClass
63 public static void postTestCleanup() throws Exception {
64 clearTests();
65 }
Madan Jampani5e5b3d62016-02-01 16:03:33 -080066 @Override
67 protected ResourceType resourceType() {
68 return new ResourceType(AtomixConsistentMap.class);
69 }
70
71 /**
72 * Tests various basic map operations.
73 */
74 @Test
75 public void testBasicMapOperations() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080076 basicMapOperationTests(3);
77 }
78
79 /**
80 * Tests various map compute* operations on different cluster sizes.
81 */
82 @Test
83 public void testMapComputeOperations() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080084 mapComputeOperationTests(3);
85 }
86
87 /**
88 * Tests map event notifications.
89 */
90 @Test
91 public void testMapListeners() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080092 mapListenerTests(3);
93 }
94
95 /**
96 * Tests map transaction commit.
97 */
98 @Test
99 public void testTransactionCommit() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800100 transactionCommitTests(3);
101 }
102
103 /**
104 * Tests map transaction rollback.
105 */
106 @Test
107 public void testTransactionRollback() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800108 transactionRollbackTests(3);
109 }
110
111 protected void basicMapOperationTests(int clusterSize) throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800112 final byte[] rawFooValue = Tools.getBytesUtf8("Hello foo!");
113 final byte[] rawBarValue = Tools.getBytesUtf8("Hello bar!");
114
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700115 AtomixConsistentMap map = createAtomixClient().getResource("testBasicMapOperationMap",
116 AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800117
118 map.isEmpty().thenAccept(result -> {
119 assertTrue(result);
120 }).join();
121
122 map.put("foo", rawFooValue).thenAccept(result -> {
123 assertNull(result);
124 }).join();
125
126 map.size().thenAccept(result -> {
127 assertTrue(result == 1);
128 }).join();
129
130 map.isEmpty().thenAccept(result -> {
131 assertFalse(result);
132 }).join();
133
134 map.putIfAbsent("foo", "Hello foo again!".getBytes()).thenAccept(result -> {
135 assertNotNull(result);
136 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
137 }).join();
138
139 map.putIfAbsent("bar", rawBarValue).thenAccept(result -> {
140 assertNull(result);
141 }).join();
142
143 map.size().thenAccept(result -> {
144 assertTrue(result == 2);
145 }).join();
146
147 map.keySet().thenAccept(result -> {
148 assertTrue(result.size() == 2);
149 assertTrue(result.containsAll(Sets.newHashSet("foo", "bar")));
150 }).join();
151
152 map.values().thenAccept(result -> {
153 assertTrue(result.size() == 2);
154 List<String> rawValues =
155 result.stream().map(v -> Tools.toStringUtf8(v.value())).collect(Collectors.toList());
156 assertTrue(rawValues.contains("Hello foo!"));
157 assertTrue(rawValues.contains("Hello bar!"));
158 }).join();
159
160 map.entrySet().thenAccept(result -> {
161 assertTrue(result.size() == 2);
162 // TODO: check entries
163 }).join();
164
165 map.get("foo").thenAccept(result -> {
166 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
167 }).join();
168
169 map.remove("foo").thenAccept(result -> {
170 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
171 }).join();
172
173 map.containsKey("foo").thenAccept(result -> {
174 assertFalse(result);
175 }).join();
176
177 map.get("foo").thenAccept(result -> {
178 assertNull(result);
179 }).join();
180
181 map.get("bar").thenAccept(result -> {
182 assertNotNull(result);
183 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
184 }).join();
185
186 map.containsKey("bar").thenAccept(result -> {
187 assertTrue(result);
188 }).join();
189
190 map.size().thenAccept(result -> {
191 assertTrue(result == 1);
192 }).join();
193
194 map.containsValue(rawBarValue).thenAccept(result -> {
195 assertTrue(result);
196 }).join();
197
198 map.containsValue(rawFooValue).thenAccept(result -> {
199 assertFalse(result);
200 }).join();
201
202 map.replace("bar", "Goodbye bar!".getBytes()).thenAccept(result -> {
203 assertNotNull(result);
204 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
205 }).join();
206
207 map.replace("foo", "Goodbye foo!".getBytes()).thenAccept(result -> {
208 assertNull(result);
209 }).join();
210
211 // try replace_if_value_match for a non-existent key
212 map.replace("foo", "Goodbye foo!".getBytes(), rawFooValue).thenAccept(result -> {
213 assertFalse(result);
214 }).join();
215
216 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
217 assertTrue(result);
218 }).join();
219
220 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
221 assertFalse(result);
222 }).join();
223
224 Versioned<byte[]> barValue = map.get("bar").join();
225 map.replace("bar", barValue.version(), "Goodbye bar!".getBytes()).thenAccept(result -> {
226 assertTrue(result);
227 }).join();
228
229 map.replace("bar", barValue.version(), rawBarValue).thenAccept(result -> {
230 assertFalse(result);
231 }).join();
232
233 map.clear().join();
234
235 map.size().thenAccept(result -> {
236 assertTrue(result == 0);
237 }).join();
238 }
239
240 public void mapComputeOperationTests(int clusterSize) throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800241 final byte[] value1 = Tools.getBytesUtf8("value1");
242 final byte[] value2 = Tools.getBytesUtf8("value2");
243 final byte[] value3 = Tools.getBytesUtf8("value3");
244
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700245 AtomixConsistentMap map = createAtomixClient().getResource("testMapComputeOperationsMap",
246 AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800247
248 map.computeIfAbsent("foo", k -> value1).thenAccept(result -> {
249 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
250 }).join();
251
252 map.computeIfAbsent("foo", k -> value2).thenAccept(result -> {
253 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
254 }).join();
255
256 map.computeIfPresent("bar", (k, v) -> value2).thenAccept(result -> {
257 assertNull(result);
258 });
259
260 map.computeIfPresent("foo", (k, v) -> value3).thenAccept(result -> {
261 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value3));
262 }).join();
263
264 map.computeIfPresent("foo", (k, v) -> null).thenAccept(result -> {
265 assertNull(result);
266 }).join();
267
268 map.computeIf("foo", v -> v == null, (k, v) -> value1).thenAccept(result -> {
269 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
270 }).join();
271
272 map.compute("foo", (k, v) -> value2).thenAccept(result -> {
273 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value2));
274 }).join();
275 }
276
277
278 protected void mapListenerTests(int clusterSize) throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800279 final byte[] value1 = Tools.getBytesUtf8("value1");
280 final byte[] value2 = Tools.getBytesUtf8("value2");
281 final byte[] value3 = Tools.getBytesUtf8("value3");
282
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700283 AtomixConsistentMap map = createAtomixClient().getResource("testMapListenerMap",
284 AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800285 TestMapEventListener listener = new TestMapEventListener();
286
287 // add listener; insert new value into map and verify an INSERT event is received.
Madan Jampani40f022e2016-03-02 21:35:14 -0800288 map.addListener(listener).thenCompose(v -> map.put("foo", value1)).join();
289 MapEvent<String, byte[]> event = listener.event();
290 assertNotNull(event);
291 assertEquals(MapEvent.Type.INSERT, event.type());
292 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800293
294 // remove listener and verify listener is not notified.
Madan Jampani40f022e2016-03-02 21:35:14 -0800295 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
296 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800297
298 // add the listener back and verify UPDATE events are received correctly
Madan Jampani40f022e2016-03-02 21:35:14 -0800299 map.addListener(listener).thenCompose(v -> map.put("foo", value3)).join();
300 event = listener.event();
301 assertNotNull(event);
302 assertEquals(MapEvent.Type.UPDATE, event.type());
303 assertTrue(Arrays.equals(value3, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800304
305 // perform a non-state changing operation and verify no events are received.
306 map.putIfAbsent("foo", value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800307 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800308
309 // verify REMOVE events are received correctly.
310 map.remove("foo").join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800311 event = listener.event();
312 assertNotNull(event);
313 assertEquals(MapEvent.Type.REMOVE, event.type());
314 assertTrue(Arrays.equals(value3, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800315
316 // verify compute methods also generate events.
317 map.computeIf("foo", v -> v == null, (k, v) -> value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800318 event = listener.event();
319 assertNotNull(event);
320 assertEquals(MapEvent.Type.INSERT, event.type());
321 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800322
323 map.compute("foo", (k, v) -> value2).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800324 event = listener.event();
325 assertNotNull(event);
326 assertEquals(MapEvent.Type.UPDATE, event.type());
327 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800328
329 map.computeIf("foo", v -> Arrays.equals(v, value2), (k, v) -> null).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800330 event = listener.event();
331 assertNotNull(event);
332 assertEquals(MapEvent.Type.REMOVE, event.type());
333 assertTrue(Arrays.equals(value2, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800334
335 map.removeListener(listener).join();
336 }
337
338 protected void transactionCommitTests(int clusterSize) throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800339 final byte[] value1 = Tools.getBytesUtf8("value1");
340 final byte[] value2 = Tools.getBytesUtf8("value2");
341
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700342 AtomixConsistentMap map = createAtomixClient().getResource("testCommitTestsMap",
343 AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800344 TestMapEventListener listener = new TestMapEventListener();
345
346 map.addListener(listener).join();
347
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700348 // PUT_IF_ABSENT
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800349 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();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700360 // verify changes in Tx is not visible yet until commit
Madan Jampani40f022e2016-03-02 21:35:14 -0800361 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800362
363 map.size().thenAccept(result -> {
364 assertTrue(result == 0);
365 }).join();
366
367 map.get("foo").thenAccept(result -> {
368 assertNull(result);
369 }).join();
370
371 try {
372 map.put("foo", value2).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700373 fail("update to map entry in open tx should fail with Exception");
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800374 } catch (CompletionException e) {
375 assertEquals(ConcurrentModificationException.class, e.getCause().getClass());
376 }
377
Madan Jampani40f022e2016-03-02 21:35:14 -0800378 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800379
Madan Jampani74da78b2016-02-09 21:18:36 -0800380 map.commit(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800381 MapEvent<String, byte[]> event = listener.event();
382 assertNotNull(event);
383 assertEquals(MapEvent.Type.INSERT, event.type());
384 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800385
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700386 // map should be update-able after commit
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800387 map.put("foo", value2).thenAccept(result -> {
388 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
389 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800390 event = listener.event();
391 assertNotNull(event);
392 assertEquals(MapEvent.Type.UPDATE, event.type());
393 assertTrue(Arrays.equals(value2, event.newValue().value()));
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700394
395
396 // REMOVE_IF_VERSION_MATCH
397 byte[] currFoo = map.get("foo").get().value();
398 long currFooVersion = map.get("foo").get().version();
399 MapUpdate<String, byte[]> remove1 =
400 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.REMOVE_IF_VERSION_MATCH)
401 .withKey("foo")
402 .withCurrentVersion(currFooVersion)
403 .build();
404
405 tx = new MapTransaction<>(TransactionId.from("tx2"), Arrays.asList(remove1));
406
407 map.prepare(tx).thenAccept(result -> {
408 assertTrue("prepare should succeed", result);
409 }).join();
410 // verify changes in Tx is not visible yet until commit
411 assertFalse(listener.eventReceived());
412
413 map.size().thenAccept(size -> {
414 assertThat(size, is(1));
415 }).join();
416
417 map.get("foo").thenAccept(result -> {
418 assertThat(result.value(), is(currFoo));
419 }).join();
420
421 map.commit(tx.transactionId()).join();
422 event = listener.event();
423 assertNotNull(event);
424 assertEquals(MapEvent.Type.REMOVE, event.type());
425 assertArrayEquals(currFoo, event.oldValue().value());
426
427 map.size().thenAccept(size -> {
428 assertThat(size, is(0));
429 }).join();
430
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800431 }
432
433 protected void transactionRollbackTests(int clusterSize) throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800434 final byte[] value1 = Tools.getBytesUtf8("value1");
435 final byte[] value2 = Tools.getBytesUtf8("value2");
436
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700437 AtomixConsistentMap map = createAtomixClient().getResource("testTransactionRollbackTestsMap",
438 AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800439 TestMapEventListener listener = new TestMapEventListener();
440
441 map.addListener(listener).join();
442
443 MapUpdate<String, byte[]> update1 =
444 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
445 .withKey("foo")
446 .withValue(value1)
447 .build();
Madan Jampani74da78b2016-02-09 21:18:36 -0800448 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampanicadd70b2016-02-08 13:45:43 -0800449 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800450 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800451 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800452 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800453
Madan Jampani74da78b2016-02-09 21:18:36 -0800454 map.rollback(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800455 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800456
457 map.get("foo").thenAccept(result -> {
458 assertNull(result);
459 }).join();
460
461 map.put("foo", value2).thenAccept(result -> {
462 assertNull(result);
463 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800464 MapEvent<String, byte[]> event = listener.event();
465 assertNotNull(event);
466 assertEquals(MapEvent.Type.INSERT, event.type());
467 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800468 }
469
470 private static class TestMapEventListener implements MapEventListener<String, byte[]> {
471
Madan Jampani40f022e2016-03-02 21:35:14 -0800472 private final BlockingQueue<MapEvent<String, byte[]>> queue = new ArrayBlockingQueue<>(1);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800473
474 @Override
475 public void event(MapEvent<String, byte[]> event) {
Madan Jampani40f022e2016-03-02 21:35:14 -0800476 try {
477 queue.put(event);
478 } catch (InterruptedException e) {
479 Throwables.propagate(e);
480 }
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800481 }
482
Madan Jampani40f022e2016-03-02 21:35:14 -0800483 public boolean eventReceived() {
484 return !queue.isEmpty();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800485 }
486
Madan Jampani40f022e2016-03-02 21:35:14 -0800487 public MapEvent<String, byte[]> event() throws InterruptedException {
488 return queue.take();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800489 }
490 }
491}