blob: 98f7657e43cc726e24834b1aeeae76c02aa0e410 [file] [log] [blame]
Madan Jampani5e5b3d62016-02-01 16:03:33 -08001/*
2 * Copyright 2016 Open Networking Laboratory
3 *
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;
19import static org.junit.Assert.*;
20
21import java.util.Arrays;
22import java.util.ConcurrentModificationException;
23import java.util.List;
Madan Jampani40f022e2016-03-02 21:35:14 -080024import java.util.concurrent.ArrayBlockingQueue;
25import java.util.concurrent.BlockingQueue;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080026import java.util.concurrent.CompletionException;
27import java.util.stream.Collectors;
28
Madan Jampani07b54482016-03-09 10:34:18 -080029import org.junit.Ignore;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080030import 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 Jampani07b54482016-03-09 10:34:18 -080045@Ignore
Madan Jampani5e5b3d62016-02-01 16:03:33 -080046public class AtomixConsistentMapTest extends AtomixTestBase {
47
48 @Override
49 protected ResourceType resourceType() {
50 return new ResourceType(AtomixConsistentMap.class);
51 }
52
53 /**
54 * Tests various basic map operations.
55 */
56 @Test
57 public void testBasicMapOperations() throws Throwable {
58 basicMapOperationTests(1);
59 clearTests();
60 basicMapOperationTests(2);
61 clearTests();
62 basicMapOperationTests(3);
63 }
64
65 /**
66 * Tests various map compute* operations on different cluster sizes.
67 */
68 @Test
69 public void testMapComputeOperations() throws Throwable {
70 mapComputeOperationTests(1);
71 clearTests();
72 mapComputeOperationTests(2);
73 clearTests();
74 mapComputeOperationTests(3);
75 }
76
77 /**
78 * Tests map event notifications.
79 */
80 @Test
81 public void testMapListeners() throws Throwable {
82 mapListenerTests(1);
83 clearTests();
84 mapListenerTests(2);
85 clearTests();
86 mapListenerTests(3);
87 }
88
89 /**
90 * Tests map transaction commit.
91 */
92 @Test
93 public void testTransactionCommit() throws Throwable {
94 transactionCommitTests(1);
95 clearTests();
96 transactionCommitTests(2);
97 clearTests();
98 transactionCommitTests(3);
99 }
100
101 /**
102 * Tests map transaction rollback.
103 */
104 @Test
105 public void testTransactionRollback() throws Throwable {
106 transactionRollbackTests(1);
107 clearTests();
108 transactionRollbackTests(2);
109 clearTests();
110 transactionRollbackTests(3);
111 }
112
113 protected void basicMapOperationTests(int clusterSize) throws Throwable {
114 createCopycatServers(clusterSize);
115
116 final byte[] rawFooValue = Tools.getBytesUtf8("Hello foo!");
117 final byte[] rawBarValue = Tools.getBytesUtf8("Hello bar!");
118
Madan Jampani65f24bb2016-03-15 15:16:18 -0700119 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800120
121 map.isEmpty().thenAccept(result -> {
122 assertTrue(result);
123 }).join();
124
125 map.put("foo", rawFooValue).thenAccept(result -> {
126 assertNull(result);
127 }).join();
128
129 map.size().thenAccept(result -> {
130 assertTrue(result == 1);
131 }).join();
132
133 map.isEmpty().thenAccept(result -> {
134 assertFalse(result);
135 }).join();
136
137 map.putIfAbsent("foo", "Hello foo again!".getBytes()).thenAccept(result -> {
138 assertNotNull(result);
139 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
140 }).join();
141
142 map.putIfAbsent("bar", rawBarValue).thenAccept(result -> {
143 assertNull(result);
144 }).join();
145
146 map.size().thenAccept(result -> {
147 assertTrue(result == 2);
148 }).join();
149
150 map.keySet().thenAccept(result -> {
151 assertTrue(result.size() == 2);
152 assertTrue(result.containsAll(Sets.newHashSet("foo", "bar")));
153 }).join();
154
155 map.values().thenAccept(result -> {
156 assertTrue(result.size() == 2);
157 List<String> rawValues =
158 result.stream().map(v -> Tools.toStringUtf8(v.value())).collect(Collectors.toList());
159 assertTrue(rawValues.contains("Hello foo!"));
160 assertTrue(rawValues.contains("Hello bar!"));
161 }).join();
162
163 map.entrySet().thenAccept(result -> {
164 assertTrue(result.size() == 2);
165 // TODO: check entries
166 }).join();
167
168 map.get("foo").thenAccept(result -> {
169 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
170 }).join();
171
172 map.remove("foo").thenAccept(result -> {
173 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
174 }).join();
175
176 map.containsKey("foo").thenAccept(result -> {
177 assertFalse(result);
178 }).join();
179
180 map.get("foo").thenAccept(result -> {
181 assertNull(result);
182 }).join();
183
184 map.get("bar").thenAccept(result -> {
185 assertNotNull(result);
186 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
187 }).join();
188
189 map.containsKey("bar").thenAccept(result -> {
190 assertTrue(result);
191 }).join();
192
193 map.size().thenAccept(result -> {
194 assertTrue(result == 1);
195 }).join();
196
197 map.containsValue(rawBarValue).thenAccept(result -> {
198 assertTrue(result);
199 }).join();
200
201 map.containsValue(rawFooValue).thenAccept(result -> {
202 assertFalse(result);
203 }).join();
204
205 map.replace("bar", "Goodbye bar!".getBytes()).thenAccept(result -> {
206 assertNotNull(result);
207 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
208 }).join();
209
210 map.replace("foo", "Goodbye foo!".getBytes()).thenAccept(result -> {
211 assertNull(result);
212 }).join();
213
214 // try replace_if_value_match for a non-existent key
215 map.replace("foo", "Goodbye foo!".getBytes(), rawFooValue).thenAccept(result -> {
216 assertFalse(result);
217 }).join();
218
219 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
220 assertTrue(result);
221 }).join();
222
223 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
224 assertFalse(result);
225 }).join();
226
227 Versioned<byte[]> barValue = map.get("bar").join();
228 map.replace("bar", barValue.version(), "Goodbye bar!".getBytes()).thenAccept(result -> {
229 assertTrue(result);
230 }).join();
231
232 map.replace("bar", barValue.version(), rawBarValue).thenAccept(result -> {
233 assertFalse(result);
234 }).join();
235
236 map.clear().join();
237
238 map.size().thenAccept(result -> {
239 assertTrue(result == 0);
240 }).join();
241 }
242
243 public void mapComputeOperationTests(int clusterSize) throws Throwable {
244 createCopycatServers(clusterSize);
245 final byte[] value1 = Tools.getBytesUtf8("value1");
246 final byte[] value2 = Tools.getBytesUtf8("value2");
247 final byte[] value3 = Tools.getBytesUtf8("value3");
248
Madan Jampani65f24bb2016-03-15 15:16:18 -0700249 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800250
251 map.computeIfAbsent("foo", k -> value1).thenAccept(result -> {
252 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
253 }).join();
254
255 map.computeIfAbsent("foo", k -> value2).thenAccept(result -> {
256 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
257 }).join();
258
259 map.computeIfPresent("bar", (k, v) -> value2).thenAccept(result -> {
260 assertNull(result);
261 });
262
263 map.computeIfPresent("foo", (k, v) -> value3).thenAccept(result -> {
264 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value3));
265 }).join();
266
267 map.computeIfPresent("foo", (k, v) -> null).thenAccept(result -> {
268 assertNull(result);
269 }).join();
270
271 map.computeIf("foo", v -> v == null, (k, v) -> value1).thenAccept(result -> {
272 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
273 }).join();
274
275 map.compute("foo", (k, v) -> value2).thenAccept(result -> {
276 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value2));
277 }).join();
278 }
279
280
281 protected void mapListenerTests(int clusterSize) throws Throwable {
282 createCopycatServers(clusterSize);
283 final byte[] value1 = Tools.getBytesUtf8("value1");
284 final byte[] value2 = Tools.getBytesUtf8("value2");
285 final byte[] value3 = Tools.getBytesUtf8("value3");
286
Madan Jampani65f24bb2016-03-15 15:16:18 -0700287 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800288 TestMapEventListener listener = new TestMapEventListener();
289
290 // add listener; insert new value into map and verify an INSERT event is received.
Madan Jampani40f022e2016-03-02 21:35:14 -0800291 map.addListener(listener).thenCompose(v -> map.put("foo", value1)).join();
292 MapEvent<String, byte[]> event = listener.event();
293 assertNotNull(event);
294 assertEquals(MapEvent.Type.INSERT, event.type());
295 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800296
297 // remove listener and verify listener is not notified.
Madan Jampani40f022e2016-03-02 21:35:14 -0800298 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
299 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800300
301 // add the listener back and verify UPDATE events are received correctly
Madan Jampani40f022e2016-03-02 21:35:14 -0800302 map.addListener(listener).thenCompose(v -> map.put("foo", value3)).join();
303 event = listener.event();
304 assertNotNull(event);
305 assertEquals(MapEvent.Type.UPDATE, event.type());
306 assertTrue(Arrays.equals(value3, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800307
308 // perform a non-state changing operation and verify no events are received.
309 map.putIfAbsent("foo", value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800310 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800311
312 // verify REMOVE events are received correctly.
313 map.remove("foo").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(value3, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800318
319 // verify compute methods also generate events.
320 map.computeIf("foo", v -> v == null, (k, v) -> value1).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800321 event = listener.event();
322 assertNotNull(event);
323 assertEquals(MapEvent.Type.INSERT, event.type());
324 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800325
326 map.compute("foo", (k, v) -> value2).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800327 event = listener.event();
328 assertNotNull(event);
329 assertEquals(MapEvent.Type.UPDATE, event.type());
330 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800331
332 map.computeIf("foo", v -> Arrays.equals(v, value2), (k, v) -> null).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800333 event = listener.event();
334 assertNotNull(event);
335 assertEquals(MapEvent.Type.REMOVE, event.type());
336 assertTrue(Arrays.equals(value2, event.oldValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800337
338 map.removeListener(listener).join();
339 }
340
341 protected void transactionCommitTests(int clusterSize) throws Throwable {
342 createCopycatServers(clusterSize);
343 final byte[] value1 = Tools.getBytesUtf8("value1");
344 final byte[] value2 = Tools.getBytesUtf8("value2");
345
Madan Jampani65f24bb2016-03-15 15:16:18 -0700346 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800347 TestMapEventListener listener = new TestMapEventListener();
348
349 map.addListener(listener).join();
350
351 MapUpdate<String, byte[]> update1 =
352 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
353 .withKey("foo")
354 .withValue(value1)
355 .build();
356
Madan Jampani74da78b2016-02-09 21:18:36 -0800357 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800358
Madan Jampanicadd70b2016-02-08 13:45:43 -0800359 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800360 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800361 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800362 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800363
364 map.size().thenAccept(result -> {
365 assertTrue(result == 0);
366 }).join();
367
368 map.get("foo").thenAccept(result -> {
369 assertNull(result);
370 }).join();
371
372 try {
373 map.put("foo", value2).join();
374 assertTrue(false);
375 } catch (CompletionException e) {
376 assertEquals(ConcurrentModificationException.class, e.getCause().getClass());
377 }
378
Madan Jampani40f022e2016-03-02 21:35:14 -0800379 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800380
Madan Jampani74da78b2016-02-09 21:18:36 -0800381 map.commit(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800382 MapEvent<String, byte[]> event = listener.event();
383 assertNotNull(event);
384 assertEquals(MapEvent.Type.INSERT, event.type());
385 assertTrue(Arrays.equals(value1, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800386
387 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()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800394 }
395
396 protected void transactionRollbackTests(int clusterSize) throws Throwable {
397 createCopycatServers(clusterSize);
398 final byte[] value1 = Tools.getBytesUtf8("value1");
399 final byte[] value2 = Tools.getBytesUtf8("value2");
400
Madan Jampani65f24bb2016-03-15 15:16:18 -0700401 AtomixConsistentMap map = createAtomixClient().getResource("test", AtomixConsistentMap.class).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800402 TestMapEventListener listener = new TestMapEventListener();
403
404 map.addListener(listener).join();
405
406 MapUpdate<String, byte[]> update1 =
407 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
408 .withKey("foo")
409 .withValue(value1)
410 .build();
Madan Jampani74da78b2016-02-09 21:18:36 -0800411 MapTransaction<String, byte[]> tx = new MapTransaction<>(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampanicadd70b2016-02-08 13:45:43 -0800412 map.prepare(tx).thenAccept(result -> {
Madan Jampani74da78b2016-02-09 21:18:36 -0800413 assertEquals(true, result);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800414 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800415 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800416
Madan Jampani74da78b2016-02-09 21:18:36 -0800417 map.rollback(tx.transactionId()).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800418 assertFalse(listener.eventReceived());
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800419
420 map.get("foo").thenAccept(result -> {
421 assertNull(result);
422 }).join();
423
424 map.put("foo", value2).thenAccept(result -> {
425 assertNull(result);
426 }).join();
Madan Jampani40f022e2016-03-02 21:35:14 -0800427 MapEvent<String, byte[]> event = listener.event();
428 assertNotNull(event);
429 assertEquals(MapEvent.Type.INSERT, event.type());
430 assertTrue(Arrays.equals(value2, event.newValue().value()));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800431 }
432
433 private static class TestMapEventListener implements MapEventListener<String, byte[]> {
434
Madan Jampani40f022e2016-03-02 21:35:14 -0800435 private final BlockingQueue<MapEvent<String, byte[]>> queue = new ArrayBlockingQueue<>(1);
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800436
437 @Override
438 public void event(MapEvent<String, byte[]> event) {
Madan Jampani40f022e2016-03-02 21:35:14 -0800439 try {
440 queue.put(event);
441 } catch (InterruptedException e) {
442 Throwables.propagate(e);
443 }
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800444 }
445
Madan Jampani40f022e2016-03-02 21:35:14 -0800446 public boolean eventReceived() {
447 return !queue.isEmpty();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800448 }
449
Madan Jampani40f022e2016-03-02 21:35:14 -0800450 public MapEvent<String, byte[]> event() throws InterruptedException {
451 return queue.take();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800452 }
453 }
454}