blob: 0ec21f76cd7977ebeb2edd14a26bb012b12facc6 [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
Madan Jampani9a624592016-06-03 10:14:38 -070030import org.junit.Ignore;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080031import org.junit.Test;
32import org.onlab.util.Tools;
Madan Jampani74da78b2016-02-09 21:18:36 -080033import org.onosproject.store.primitives.MapUpdate;
Madan Jampanicadd70b2016-02-08 13:45:43 -080034import org.onosproject.store.primitives.TransactionId;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080035import org.onosproject.store.service.MapEvent;
36import org.onosproject.store.service.MapEventListener;
Madan Jampani74da78b2016-02-09 21:18:36 -080037import org.onosproject.store.service.MapTransaction;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080038import org.onosproject.store.service.Versioned;
39
Madan Jampani40f022e2016-03-02 21:35:14 -080040import com.google.common.base.Throwables;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080041import com.google.common.collect.Sets;
42
43/**
44 * Unit tests for {@link AtomixConsistentMap}.
45 */
Madan Jampani9a624592016-06-03 10:14:38 -070046@Ignore
Madan Jampani5e5b3d62016-02-01 16:03:33 -080047public class AtomixConsistentMapTest extends AtomixTestBase {
48
49 @Override
50 protected ResourceType resourceType() {
51 return new ResourceType(AtomixConsistentMap.class);
52 }
53
54 /**
55 * Tests various basic map operations.
56 */
57 @Test
58 public void testBasicMapOperations() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080059 basicMapOperationTests(3);
60 }
61
62 /**
63 * Tests various map compute* operations on different cluster sizes.
64 */
65 @Test
66 public void testMapComputeOperations() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080067 mapComputeOperationTests(3);
68 }
69
70 /**
71 * Tests map event notifications.
72 */
73 @Test
74 public void testMapListeners() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080075 mapListenerTests(3);
76 }
77
78 /**
79 * Tests map transaction commit.
80 */
81 @Test
82 public void testTransactionCommit() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080083 transactionCommitTests(3);
84 }
85
86 /**
87 * Tests map transaction rollback.
88 */
89 @Test
90 public void testTransactionRollback() throws Throwable {
Madan Jampani5e5b3d62016-02-01 16:03:33 -080091 transactionRollbackTests(3);
92 }
93
94 protected void basicMapOperationTests(int clusterSize) throws Throwable {
95 createCopycatServers(clusterSize);
96
97 final byte[] rawFooValue = Tools.getBytesUtf8("Hello foo!");
98 final byte[] rawBarValue = Tools.getBytesUtf8("Hello bar!");
99
Madan Jampani65f24bb2016-03-15 15:16:18 -0700100 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800101
102 map.isEmpty().thenAccept(result -> {
103 assertTrue(result);
104 }).join();
105
106 map.put("foo", rawFooValue).thenAccept(result -> {
107 assertNull(result);
108 }).join();
109
110 map.size().thenAccept(result -> {
111 assertTrue(result == 1);
112 }).join();
113
114 map.isEmpty().thenAccept(result -> {
115 assertFalse(result);
116 }).join();
117
118 map.putIfAbsent("foo", "Hello foo again!".getBytes()).thenAccept(result -> {
119 assertNotNull(result);
120 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
121 }).join();
122
123 map.putIfAbsent("bar", rawBarValue).thenAccept(result -> {
124 assertNull(result);
125 }).join();
126
127 map.size().thenAccept(result -> {
128 assertTrue(result == 2);
129 }).join();
130
131 map.keySet().thenAccept(result -> {
132 assertTrue(result.size() == 2);
133 assertTrue(result.containsAll(Sets.newHashSet("foo", "bar")));
134 }).join();
135
136 map.values().thenAccept(result -> {
137 assertTrue(result.size() == 2);
138 List<String> rawValues =
139 result.stream().map(v -> Tools.toStringUtf8(v.value())).collect(Collectors.toList());
140 assertTrue(rawValues.contains("Hello foo!"));
141 assertTrue(rawValues.contains("Hello bar!"));
142 }).join();
143
144 map.entrySet().thenAccept(result -> {
145 assertTrue(result.size() == 2);
146 // TODO: check entries
147 }).join();
148
149 map.get("foo").thenAccept(result -> {
150 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
151 }).join();
152
153 map.remove("foo").thenAccept(result -> {
154 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
155 }).join();
156
157 map.containsKey("foo").thenAccept(result -> {
158 assertFalse(result);
159 }).join();
160
161 map.get("foo").thenAccept(result -> {
162 assertNull(result);
163 }).join();
164
165 map.get("bar").thenAccept(result -> {
166 assertNotNull(result);
167 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
168 }).join();
169
170 map.containsKey("bar").thenAccept(result -> {
171 assertTrue(result);
172 }).join();
173
174 map.size().thenAccept(result -> {
175 assertTrue(result == 1);
176 }).join();
177
178 map.containsValue(rawBarValue).thenAccept(result -> {
179 assertTrue(result);
180 }).join();
181
182 map.containsValue(rawFooValue).thenAccept(result -> {
183 assertFalse(result);
184 }).join();
185
186 map.replace("bar", "Goodbye bar!".getBytes()).thenAccept(result -> {
187 assertNotNull(result);
188 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
189 }).join();
190
191 map.replace("foo", "Goodbye foo!".getBytes()).thenAccept(result -> {
192 assertNull(result);
193 }).join();
194
195 // try replace_if_value_match for a non-existent key
196 map.replace("foo", "Goodbye foo!".getBytes(), rawFooValue).thenAccept(result -> {
197 assertFalse(result);
198 }).join();
199
200 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
201 assertTrue(result);
202 }).join();
203
204 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
205 assertFalse(result);
206 }).join();
207
208 Versioned<byte[]> barValue = map.get("bar").join();
209 map.replace("bar", barValue.version(), "Goodbye bar!".getBytes()).thenAccept(result -> {
210 assertTrue(result);
211 }).join();
212
213 map.replace("bar", barValue.version(), rawBarValue).thenAccept(result -> {
214 assertFalse(result);
215 }).join();
216
217 map.clear().join();
218
219 map.size().thenAccept(result -> {
220 assertTrue(result == 0);
221 }).join();
222 }
223
224 public void mapComputeOperationTests(int clusterSize) throws Throwable {
225 createCopycatServers(clusterSize);
226 final byte[] value1 = Tools.getBytesUtf8("value1");
227 final byte[] value2 = Tools.getBytesUtf8("value2");
228 final byte[] value3 = Tools.getBytesUtf8("value3");
229
Madan Jampani65f24bb2016-03-15 15:16:18 -0700230 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800231
232 map.computeIfAbsent("foo", k -> value1).thenAccept(result -> {
233 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
234 }).join();
235
236 map.computeIfAbsent("foo", k -> value2).thenAccept(result -> {
237 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
238 }).join();
239
240 map.computeIfPresent("bar", (k, v) -> value2).thenAccept(result -> {
241 assertNull(result);
242 });
243
244 map.computeIfPresent("foo", (k, v) -> value3).thenAccept(result -> {
245 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value3));
246 }).join();
247
248 map.computeIfPresent("foo", (k, v) -> null).thenAccept(result -> {
249 assertNull(result);
250 }).join();
251
252 map.computeIf("foo", v -> v == null, (k, v) -> value1).thenAccept(result -> {
253 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
254 }).join();
255
256 map.compute("foo", (k, v) -> value2).thenAccept(result -> {
257 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value2));
258 }).join();
259 }
260
261
262 protected void mapListenerTests(int clusterSize) throws Throwable {
263 createCopycatServers(clusterSize);
264 final byte[] value1 = Tools.getBytesUtf8("value1");
265 final byte[] value2 = Tools.getBytesUtf8("value2");
266 final byte[] value3 = Tools.getBytesUtf8("value3");
267
Madan Jampani65f24bb2016-03-15 15:16:18 -0700268 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800269 TestMapEventListener listener = new TestMapEventListener();
270
271 // add listener; insert new value into map and verify an INSERT event is received.
Madan Jampani40f022e2016-03-02 21:35:14 -0800272 map.addListener(listener).thenCompose(v -> map.put("foo", value1)).join();
273 MapEvent<String, byte[]> event = listener.event();
274 assertNotNull(event);
275 assertEquals(MapEvent.Type.INSERT, event.type());
276 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800277
278 // remove listener and verify listener is not notified.
Madan Jampani40f022e2016-03-02 21:35:14 -0800279 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
280 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800281
282 // add the listener back and verify UPDATE events are received correctly
Madan Jampani40f022e2016-03-02 21:35:14 -0800283 map.addListener(listener).thenCompose(v -> map.put("foo", value3)).join();
284 event = listener.event();
285 assertNotNull(event);
286 assertEquals(MapEvent.Type.UPDATE, event.type());
287 assertTrue(Arrays.equals(value3, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800288
289 // perform a non-state changing operation and verify no events are received.
290 map.putIfAbsent("foo", value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800291 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800292
293 // verify REMOVE events are received correctly.
294 map.remove("foo").join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800295 event = listener.event();
296 assertNotNull(event);
297 assertEquals(MapEvent.Type.REMOVE, event.type());
298 assertTrue(Arrays.equals(value3, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800299
300 // verify compute methods also generate events.
301 map.computeIf("foo", v -> v == null, (k, v) -> value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800302 event = listener.event();
303 assertNotNull(event);
304 assertEquals(MapEvent.Type.INSERT, event.type());
305 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800306
307 map.compute("foo", (k, v) -> value2).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800308 event = listener.event();
309 assertNotNull(event);
310 assertEquals(MapEvent.Type.UPDATE, event.type());
311 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800312
313 map.computeIf("foo", v -> Arrays.equals(v, value2), (k, v) -> null).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800314 event = listener.event();
315 assertNotNull(event);
316 assertEquals(MapEvent.Type.REMOVE, event.type());
317 assertTrue(Arrays.equals(value2, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800318
319 map.removeListener(listener).join();
320 }
321
322 protected void transactionCommitTests(int clusterSize) throws Throwable {
323 createCopycatServers(clusterSize);
324 final byte[] value1 = Tools.getBytesUtf8("value1");
325 final byte[] value2 = Tools.getBytesUtf8("value2");
326
Madan Jampani65f24bb2016-03-15 15:16:18 -0700327 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800328 TestMapEventListener listener = new TestMapEventListener();
329
330 map.addListener(listener).join();
331
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700332 // PUT_IF_ABSENT
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800333 MapUpdate<String, byte[]> update1 =
334 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
335 .withKey("foo")
336 .withValue(value1)
337 .build();
338
Madan Jampani74da78b2016-02-09 21:18:36 -0800339 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800340
Madan Jampanicadd70b2016-02-08 13:45:43 -0800341 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800342 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800343 }).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700344 // verify changes in Tx is not visible yet until commit
Madan Jampani40f022e2016-03-02 21:35:14 -0800345 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800346
347 map.size().thenAccept(result -> {
348 assertTrue(result == 0);
349 }).join();
350
351 map.get("foo").thenAccept(result -> {
352 assertNull(result);
353 }).join();
354
355 try {
356 map.put("foo", value2).join();
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700357 fail("update to map entry in open tx should fail with Exception");
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800358 } catch (CompletionException e) {
359 assertEquals(ConcurrentModificationException.class, e.getCause().getClass());
360 }
361
Madan Jampani40f022e2016-03-02 21:35:14 -0800362 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800363
Madan Jampani74da78b2016-02-09 21:18:36 -0800364 map.commit(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800365 MapEvent<String, byte[]> event = listener.event();
366 assertNotNull(event);
367 assertEquals(MapEvent.Type.INSERT, event.type());
368 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800369
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700370 // map should be update-able after commit
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800371 map.put("foo", value2).thenAccept(result -> {
372 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
373 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800374 event = listener.event();
375 assertNotNull(event);
376 assertEquals(MapEvent.Type.UPDATE, event.type());
377 assertTrue(Arrays.equals(value2, event.newValue().value()));
HIGUCHI Yutacaad26b2016-04-16 16:06:11 -0700378
379
380 // REMOVE_IF_VERSION_MATCH
381 byte[] currFoo = map.get("foo").get().value();
382 long currFooVersion = map.get("foo").get().version();
383 MapUpdate<String, byte[]> remove1 =
384 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.REMOVE_IF_VERSION_MATCH)
385 .withKey("foo")
386 .withCurrentVersion(currFooVersion)
387 .build();
388
389 tx = new MapTransaction<>(TransactionId.from("tx2"), Arrays.asList(remove1));
390
391 map.prepare(tx).thenAccept(result -> {
392 assertTrue("prepare should succeed", result);
393 }).join();
394 // verify changes in Tx is not visible yet until commit
395 assertFalse(listener.eventReceived());
396
397 map.size().thenAccept(size -> {
398 assertThat(size, is(1));
399 }).join();
400
401 map.get("foo").thenAccept(result -> {
402 assertThat(result.value(), is(currFoo));
403 }).join();
404
405 map.commit(tx.transactionId()).join();
406 event = listener.event();
407 assertNotNull(event);
408 assertEquals(MapEvent.Type.REMOVE, event.type());
409 assertArrayEquals(currFoo, event.oldValue().value());
410
411 map.size().thenAccept(size -> {
412 assertThat(size, is(0));
413 }).join();
414
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800415 }
416
417 protected void transactionRollbackTests(int clusterSize) throws Throwable {
418 createCopycatServers(clusterSize);
419 final byte[] value1 = Tools.getBytesUtf8("value1");
420 final byte[] value2 = Tools.getBytesUtf8("value2");
421
Madan Jampani65f24bb2016-03-15 15:16:18 -0700422 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800423 TestMapEventListener listener = new TestMapEventListener();
424
425 map.addListener(listener).join();
426
427 MapUpdate<String, byte[]> update1 =
428 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
429 .withKey("foo")
430 .withValue(value1)
431 .build();
Madan Jampani74da78b2016-02-09 21:18:36 -0800432 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampanicadd70b2016-02-08 13:45:43 -0800433 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800434 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800435 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800436 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800437
Madan Jampani74da78b2016-02-09 21:18:36 -0800438 map.rollback(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800439 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800440
441 map.get("foo").thenAccept(result -> {
442 assertNull(result);
443 }).join();
444
445 map.put("foo", value2).thenAccept(result -> {
446 assertNull(result);
447 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800448 MapEvent<String, byte[]> event = listener.event();
449 assertNotNull(event);
450 assertEquals(MapEvent.Type.INSERT, event.type());
451 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800452 }
453
454 private static class TestMapEventListener implements MapEventListener<String, byte[]> {
455
Madan Jampani40f022e2016-03-02 21:35:14 -0800456 private final BlockingQueue<MapEvent<String, byte[]>> queue = new ArrayBlockingQueue<>(1);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800457
458 @Override
459 public void event(MapEvent<String, byte[]> event) {
Madan Jampani40f022e2016-03-02 21:35:14 -0800460 try {
461 queue.put(event);
462 } catch (InterruptedException e) {
463 Throwables.propagate(e);
464 }
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800465 }
466
Madan Jampani40f022e2016-03-02 21:35:14 -0800467 public boolean eventReceived() {
468 return !queue.isEmpty();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800469 }
470
Madan Jampani40f022e2016-03-02 21:35:14 -0800471 public MapEvent<String, byte[]> event() throws InterruptedException {
472 return queue.take();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800473 }
474 }
475}