blob: 0787c1e8268200406ff63beced56e94ada742033 [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 -070019import static org.hamcrest.Matchers.*;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080020import static org.junit.Assert.*;
21
22import java.util.Arrays;
23import java.util.ConcurrentModificationException;
24import java.util.List;
Madan Jampani40f022e2016-03-02 21:35:14 -080025import java.util.concurrent.ArrayBlockingQueue;
26import java.util.concurrent.BlockingQueue;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080027import java.util.concurrent.CompletionException;
28import java.util.stream.Collectors;
29
30import org.junit.Test;
31import org.onlab.util.Tools;
Madan Jampani74da78b2016-02-09 21:18:36 -080032import org.onosproject.store.primitives.MapUpdate;
Madan Jampanicadd70b2016-02-08 13:45:43 -080033import org.onosproject.store.primitives.TransactionId;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080034import org.onosproject.store.service.MapEvent;
35import org.onosproject.store.service.MapEventListener;
Madan Jampani74da78b2016-02-09 21:18:36 -080036import org.onosproject.store.service.MapTransaction;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080037import org.onosproject.store.service.Versioned;
38
Madan Jampani40f022e2016-03-02 21:35:14 -080039import com.google.common.base.Throwables;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080040import com.google.common.collect.Sets;
41
42/**
43 * Unit tests for {@link AtomixConsistentMap}.
44 */
Madan Jampani5e5b3d62016-02-01 16:03:33 -080045public class AtomixConsistentMapTest extends AtomixTestBase {
46
47 @Override
48 protected ResourceType resourceType() {
49 return new ResourceType(AtomixConsistentMap.class);
50 }
51
52 /**
53 * Tests various basic map operations.
54 */
55 @Test
56 public void testBasicMapOperations() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080057 basicMapOperationTests(3);
58 }
59
60 /**
61 * Tests various map compute* operations on different cluster sizes.
62 */
63 @Test
64 public void testMapComputeOperations() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080065 mapComputeOperationTests(3);
66 }
67
68 /**
69 * Tests map event notifications.
70 */
71 @Test
72 public void testMapListeners() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080073 mapListenerTests(3);
74 }
75
76 /**
77 * Tests map transaction commit.
78 */
79 @Test
80 public void testTransactionCommit() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080081 transactionCommitTests(3);
82 }
83
84 /**
85 * Tests map transaction rollback.
86 */
87 @Test
88 public void testTransactionRollback() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080089 transactionRollbackTests(3);
90 }
91
92 protected void basicMapOperationTests(int clusterSize) throws Throwable {
93 createCopycatServers(clusterSize);
94
95 final byte[] rawFooValue = Tools.getBytesUtf8("Hello foo!");
96 final byte[] rawBarValue = Tools.getBytesUtf8("Hello bar!");
97
Madan Jampani65f24bb2016-03-15 15:16:18 -070098 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -080099
100 map.isEmpty().thenAccept(result -> {
101 assertTrue(result);
102 }).join();
103
104 map.put("foo", rawFooValue).thenAccept(result -> {
105 assertNull(result);
106 }).join();
107
108 map.size().thenAccept(result -> {
109 assertTrue(result == 1);
110 }).join();
111
112 map.isEmpty().thenAccept(result -> {
113 assertFalse(result);
114 }).join();
115
116 map.putIfAbsent("foo", "Hello foo again!".getBytes()).thenAccept(result -> {
117 assertNotNull(result);
118 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
119 }).join();
120
121 map.putIfAbsent("bar", rawBarValue).thenAccept(result -> {
122 assertNull(result);
123 }).join();
124
125 map.size().thenAccept(result -> {
126 assertTrue(result == 2);
127 }).join();
128
129 map.keySet().thenAccept(result -> {
130 assertTrue(result.size() == 2);
131 assertTrue(result.containsAll(Sets.newHashSet("foo", "bar")));
132 }).join();
133
134 map.values().thenAccept(result -> {
135 assertTrue(result.size() == 2);
136 List<String> rawValues =
137 result.stream().map(v -> Tools.toStringUtf8(v.value())).collect(Collectors.toList());
138 assertTrue(rawValues.contains("Hello foo!"));
139 assertTrue(rawValues.contains("Hello bar!"));
140 }).join();
141
142 map.entrySet().thenAccept(result -> {
143 assertTrue(result.size() == 2);
144 // TODO: check entries
145 }).join();
146
147 map.get("foo").thenAccept(result -> {
148 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
149 }).join();
150
151 map.remove("foo").thenAccept(result -> {
152 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
153 }).join();
154
155 map.containsKey("foo").thenAccept(result -> {
156 assertFalse(result);
157 }).join();
158
159 map.get("foo").thenAccept(result -> {
160 assertNull(result);
161 }).join();
162
163 map.get("bar").thenAccept(result -> {
164 assertNotNull(result);
165 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
166 }).join();
167
168 map.containsKey("bar").thenAccept(result -> {
169 assertTrue(result);
170 }).join();
171
172 map.size().thenAccept(result -> {
173 assertTrue(result == 1);
174 }).join();
175
176 map.containsValue(rawBarValue).thenAccept(result -> {
177 assertTrue(result);
178 }).join();
179
180 map.containsValue(rawFooValue).thenAccept(result -> {
181 assertFalse(result);
182 }).join();
183
184 map.replace("bar", "Goodbye bar!".getBytes()).thenAccept(result -> {
185 assertNotNull(result);
186 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
187 }).join();
188
189 map.replace("foo", "Goodbye foo!".getBytes()).thenAccept(result -> {
190 assertNull(result);
191 }).join();
192
193 // try replace_if_value_match for a non-existent key
194 map.replace("foo", "Goodbye foo!".getBytes(), rawFooValue).thenAccept(result -> {
195 assertFalse(result);
196 }).join();
197
198 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
199 assertTrue(result);
200 }).join();
201
202 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
203 assertFalse(result);
204 }).join();
205
206 Versioned<byte[]> barValue = map.get("bar").join();
207 map.replace("bar", barValue.version(), "Goodbye bar!".getBytes()).thenAccept(result -> {
208 assertTrue(result);
209 }).join();
210
211 map.replace("bar", barValue.version(), rawBarValue).thenAccept(result -> {
212 assertFalse(result);
213 }).join();
214
215 map.clear().join();
216
217 map.size().thenAccept(result -> {
218 assertTrue(result == 0);
219 }).join();
220 }
221
222 public void mapComputeOperationTests(int clusterSize) throws Throwable {
223 createCopycatServers(clusterSize);
224 final byte[] value1 = Tools.getBytesUtf8("value1");
225 final byte[] value2 = Tools.getBytesUtf8("value2");
226 final byte[] value3 = Tools.getBytesUtf8("value3");
227
Madan Jampani65f24bb2016-03-15 15:16:18 -0700228 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800229
230 map.computeIfAbsent("foo", k -> value1).thenAccept(result -> {
231 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
232 }).join();
233
234 map.computeIfAbsent("foo", k -> value2).thenAccept(result -> {
235 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
236 }).join();
237
238 map.computeIfPresent("bar", (k, v) -> value2).thenAccept(result -> {
239 assertNull(result);
240 });
241
242 map.computeIfPresent("foo", (k, v) -> value3).thenAccept(result -> {
243 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value3));
244 }).join();
245
246 map.computeIfPresent("foo", (k, v) -> null).thenAccept(result -> {
247 assertNull(result);
248 }).join();
249
250 map.computeIf("foo", v -> v == null, (k, v) -> value1).thenAccept(result -> {
251 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
252 }).join();
253
254 map.compute("foo", (k, v) -> value2).thenAccept(result -> {
255 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value2));
256 }).join();
257 }
258
259
260 protected void mapListenerTests(int clusterSize) throws Throwable {
261 createCopycatServers(clusterSize);
262 final byte[] value1 = Tools.getBytesUtf8("value1");
263 final byte[] value2 = Tools.getBytesUtf8("value2");
264 final byte[] value3 = Tools.getBytesUtf8("value3");
265
Madan Jampani65f24bb2016-03-15 15:16:18 -0700266 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800267 TestMapEventListener listener = new TestMapEventListener();
268
269 // add listener; insert new value into map and verify an INSERT event is received.
Madan Jampani40f022e2016-03-02 21:35:14 -0800270 map.addListener(listener).thenCompose(v -> map.put("foo", value1)).join();
271 MapEvent<String, byte[]> event = listener.event();
272 assertNotNull(event);
273 assertEquals(MapEvent.Type.INSERT, event.type());
274 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800275
276 // remove listener and verify listener is not notified.
Madan Jampani40f022e2016-03-02 21:35:14 -0800277 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
278 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800279
280 // add the listener back and verify UPDATE events are received correctly
Madan Jampani40f022e2016-03-02 21:35:14 -0800281 map.addListener(listener).thenCompose(v -> map.put("foo", value3)).join();
282 event = listener.event();
283 assertNotNull(event);
284 assertEquals(MapEvent.Type.UPDATE, event.type());
285 assertTrue(Arrays.equals(value3, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800286
287 // perform a non-state changing operation and verify no events are received.
288 map.putIfAbsent("foo", value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800289 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800290
291 // verify REMOVE events are received correctly.
292 map.remove("foo").join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800293 event = listener.event();
294 assertNotNull(event);
295 assertEquals(MapEvent.Type.REMOVE, event.type());
296 assertTrue(Arrays.equals(value3, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800297
298 // verify compute methods also generate events.
299 map.computeIf("foo", v -> v == null, (k, v) -> value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800300 event = listener.event();
301 assertNotNull(event);
302 assertEquals(MapEvent.Type.INSERT, event.type());
303 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800304
305 map.compute("foo", (k, v) -> value2).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800306 event = listener.event();
307 assertNotNull(event);
308 assertEquals(MapEvent.Type.UPDATE, event.type());
309 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800310
311 map.computeIf("foo", v -> Arrays.equals(v, value2), (k, v) -> null).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(value2, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800316
317 map.removeListener(listener).join();
318 }
319
320 protected void transactionCommitTests(int clusterSize) throws Throwable {
321 createCopycatServers(clusterSize);
322 final byte[] value1 = Tools.getBytesUtf8("value1");
323 final byte[] value2 = Tools.getBytesUtf8("value2");
324
Madan Jampani65f24bb2016-03-15 15:16:18 -0700325 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800326 TestMapEventListener listener = new TestMapEventListener();
327
328 map.addListener(listener).join();
329
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700330 // PUT_IF_ABSENT
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800331 MapUpdate<String, byte[]> update1 =
332 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
333 .withKey("foo")
334 .withValue(value1)
335 .build();
336
Madan Jampani74da78b2016-02-09 21:18:36 -0800337 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800338
Madan Jampanicadd70b2016-02-08 13:45:43 -0800339 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800340 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800341 }).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700342 // verify changes in Tx is not visible yet until commit
Madan Jampani40f022e2016-03-02 21:35:14 -0800343 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800344
345 map.size().thenAccept(result -> {
346 assertTrue(result == 0);
347 }).join();
348
349 map.get("foo").thenAccept(result -> {
350 assertNull(result);
351 }).join();
352
353 try {
354 map.put("foo", value2).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700355 fail("update to map entry in open tx should fail with Exception");
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800356 } catch (CompletionException e) {
357 assertEquals(ConcurrentModificationException.class, e.getCause().getClass());
358 }
359
Madan Jampani40f022e2016-03-02 21:35:14 -0800360 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800361
Madan Jampani74da78b2016-02-09 21:18:36 -0800362 map.commit(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800363 MapEvent<String, byte[]> event = listener.event();
364 assertNotNull(event);
365 assertEquals(MapEvent.Type.INSERT, event.type());
366 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800367
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700368 // map should be update-able after commit
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800369 map.put("foo", value2).thenAccept(result -> {
370 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
371 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800372 event = listener.event();
373 assertNotNull(event);
374 assertEquals(MapEvent.Type.UPDATE, event.type());
375 assertTrue(Arrays.equals(value2, event.newValue().value()));
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700376
377
378 // REMOVE_IF_VERSION_MATCH
379 byte[] currFoo = map.get("foo").get().value();
380 long currFooVersion = map.get("foo").get().version();
381 MapUpdate<String, byte[]> remove1 =
382 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.REMOVE_IF_VERSION_MATCH)
383 .withKey("foo")
384 .withCurrentVersion(currFooVersion)
385 .build();
386
387 tx = new MapTransaction<>(TransactionId.from("tx2"), Arrays.asList(remove1));
388
389 map.prepare(tx).thenAccept(result -> {
390 assertTrue("prepare should succeed", result);
391 }).join();
392 // verify changes in Tx is not visible yet until commit
393 assertFalse(listener.eventReceived());
394
395 map.size().thenAccept(size -> {
396 assertThat(size, is(1));
397 }).join();
398
399 map.get("foo").thenAccept(result -> {
400 assertThat(result.value(), is(currFoo));
401 }).join();
402
403 map.commit(tx.transactionId()).join();
404 event = listener.event();
405 assertNotNull(event);
406 assertEquals(MapEvent.Type.REMOVE, event.type());
407 assertArrayEquals(currFoo, event.oldValue().value());
408
409 map.size().thenAccept(size -> {
410 assertThat(size, is(0));
411 }).join();
412
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800413 }
414
415 protected void transactionRollbackTests(int clusterSize) throws Throwable {
416 createCopycatServers(clusterSize);
417 final byte[] value1 = Tools.getBytesUtf8("value1");
418 final byte[] value2 = Tools.getBytesUtf8("value2");
419
Madan Jampani65f24bb2016-03-15 15:16:18 -0700420 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800421 TestMapEventListener listener = new TestMapEventListener();
422
423 map.addListener(listener).join();
424
425 MapUpdate<String, byte[]> update1 =
426 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
427 .withKey("foo")
428 .withValue(value1)
429 .build();
Madan Jampani74da78b2016-02-09 21:18:36 -0800430 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampanicadd70b2016-02-08 13:45:43 -0800431 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800432 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800433 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800434 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800435
Madan Jampani74da78b2016-02-09 21:18:36 -0800436 map.rollback(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800437 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800438
439 map.get("foo").thenAccept(result -> {
440 assertNull(result);
441 }).join();
442
443 map.put("foo", value2).thenAccept(result -> {
444 assertNull(result);
445 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800446 MapEvent<String, byte[]> event = listener.event();
447 assertNotNull(event);
448 assertEquals(MapEvent.Type.INSERT, event.type());
449 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800450 }
451
452 private static class TestMapEventListener implements MapEventListener<String, byte[]> {
453
Madan Jampani40f022e2016-03-02 21:35:14 -0800454 private final BlockingQueue<MapEvent<String, byte[]>> queue = new ArrayBlockingQueue<>(1);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800455
456 @Override
457 public void event(MapEvent<String, byte[]> event) {
Madan Jampani40f022e2016-03-02 21:35:14 -0800458 try {
459 queue.put(event);
460 } catch (InterruptedException e) {
461 Throwables.propagate(e);
462 }
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800463 }
464
Madan Jampani40f022e2016-03-02 21:35:14 -0800465 public boolean eventReceived() {
466 return !queue.isEmpty();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800467 }
468
Madan Jampani40f022e2016-03-02 21:35:14 -0800469 public MapEvent<String, byte[]> event() throws InterruptedException {
470 return queue.take();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800471 }
472 }
473}