blob: 21d8edc1e788830af087555240ffc65333a2bd01 [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;
24import java.util.concurrent.CompletionException;
25import java.util.stream.Collectors;
26
Madan Jampani1f0202c2016-02-03 14:30:29 -080027import org.junit.Ignore;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080028import org.junit.Test;
29import org.onlab.util.Tools;
Madan Jampanicadd70b2016-02-08 13:45:43 -080030import org.onosproject.store.primitives.TransactionId;
31import org.onosproject.store.primitives.impl.Transaction;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080032import org.onosproject.store.service.MapEvent;
33import org.onosproject.store.service.MapEventListener;
34import org.onosproject.store.service.Versioned;
35
36import com.google.common.collect.Sets;
37
38/**
39 * Unit tests for {@link AtomixConsistentMap}.
40 */
Madan Jampani1f0202c2016-02-03 14:30:29 -080041@Ignore
Madan Jampani5e5b3d62016-02-01 16:03:33 -080042public class AtomixConsistentMapTest extends AtomixTestBase {
43
44 @Override
45 protected ResourceType resourceType() {
46 return new ResourceType(AtomixConsistentMap.class);
47 }
48
49 /**
50 * Tests various basic map operations.
51 */
52 @Test
53 public void testBasicMapOperations() throws Throwable {
54 basicMapOperationTests(1);
55 clearTests();
56 basicMapOperationTests(2);
57 clearTests();
58 basicMapOperationTests(3);
59 }
60
61 /**
62 * Tests various map compute* operations on different cluster sizes.
63 */
64 @Test
65 public void testMapComputeOperations() throws Throwable {
66 mapComputeOperationTests(1);
67 clearTests();
68 mapComputeOperationTests(2);
69 clearTests();
70 mapComputeOperationTests(3);
71 }
72
73 /**
74 * Tests map event notifications.
75 */
76 @Test
77 public void testMapListeners() throws Throwable {
78 mapListenerTests(1);
79 clearTests();
80 mapListenerTests(2);
81 clearTests();
82 mapListenerTests(3);
83 }
84
85 /**
86 * Tests map transaction commit.
87 */
88 @Test
89 public void testTransactionCommit() throws Throwable {
90 transactionCommitTests(1);
91 clearTests();
92 transactionCommitTests(2);
93 clearTests();
94 transactionCommitTests(3);
95 }
96
97 /**
98 * Tests map transaction rollback.
99 */
100 @Test
101 public void testTransactionRollback() throws Throwable {
102 transactionRollbackTests(1);
103 clearTests();
104 transactionRollbackTests(2);
105 clearTests();
106 transactionRollbackTests(3);
107 }
108
109 protected void basicMapOperationTests(int clusterSize) throws Throwable {
110 createCopycatServers(clusterSize);
111
112 final byte[] rawFooValue = Tools.getBytesUtf8("Hello foo!");
113 final byte[] rawBarValue = Tools.getBytesUtf8("Hello bar!");
114
115 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
116
117 map.isEmpty().thenAccept(result -> {
118 assertTrue(result);
119 }).join();
120
121 map.put("foo", rawFooValue).thenAccept(result -> {
122 assertNull(result);
123 }).join();
124
125 map.size().thenAccept(result -> {
126 assertTrue(result == 1);
127 }).join();
128
129 map.isEmpty().thenAccept(result -> {
130 assertFalse(result);
131 }).join();
132
133 map.putIfAbsent("foo", "Hello foo again!".getBytes()).thenAccept(result -> {
134 assertNotNull(result);
135 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
136 }).join();
137
138 map.putIfAbsent("bar", rawBarValue).thenAccept(result -> {
139 assertNull(result);
140 }).join();
141
142 map.size().thenAccept(result -> {
143 assertTrue(result == 2);
144 }).join();
145
146 map.keySet().thenAccept(result -> {
147 assertTrue(result.size() == 2);
148 assertTrue(result.containsAll(Sets.newHashSet("foo", "bar")));
149 }).join();
150
151 map.values().thenAccept(result -> {
152 assertTrue(result.size() == 2);
153 List<String> rawValues =
154 result.stream().map(v -> Tools.toStringUtf8(v.value())).collect(Collectors.toList());
155 assertTrue(rawValues.contains("Hello foo!"));
156 assertTrue(rawValues.contains("Hello bar!"));
157 }).join();
158
159 map.entrySet().thenAccept(result -> {
160 assertTrue(result.size() == 2);
161 // TODO: check entries
162 }).join();
163
164 map.get("foo").thenAccept(result -> {
165 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
166 }).join();
167
168 map.remove("foo").thenAccept(result -> {
169 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawFooValue));
170 }).join();
171
172 map.containsKey("foo").thenAccept(result -> {
173 assertFalse(result);
174 }).join();
175
176 map.get("foo").thenAccept(result -> {
177 assertNull(result);
178 }).join();
179
180 map.get("bar").thenAccept(result -> {
181 assertNotNull(result);
182 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
183 }).join();
184
185 map.containsKey("bar").thenAccept(result -> {
186 assertTrue(result);
187 }).join();
188
189 map.size().thenAccept(result -> {
190 assertTrue(result == 1);
191 }).join();
192
193 map.containsValue(rawBarValue).thenAccept(result -> {
194 assertTrue(result);
195 }).join();
196
197 map.containsValue(rawFooValue).thenAccept(result -> {
198 assertFalse(result);
199 }).join();
200
201 map.replace("bar", "Goodbye bar!".getBytes()).thenAccept(result -> {
202 assertNotNull(result);
203 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), rawBarValue));
204 }).join();
205
206 map.replace("foo", "Goodbye foo!".getBytes()).thenAccept(result -> {
207 assertNull(result);
208 }).join();
209
210 // try replace_if_value_match for a non-existent key
211 map.replace("foo", "Goodbye foo!".getBytes(), rawFooValue).thenAccept(result -> {
212 assertFalse(result);
213 }).join();
214
215 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
216 assertTrue(result);
217 }).join();
218
219 map.replace("bar", "Goodbye bar!".getBytes(), rawBarValue).thenAccept(result -> {
220 assertFalse(result);
221 }).join();
222
223 Versioned<byte[]> barValue = map.get("bar").join();
224 map.replace("bar", barValue.version(), "Goodbye bar!".getBytes()).thenAccept(result -> {
225 assertTrue(result);
226 }).join();
227
228 map.replace("bar", barValue.version(), rawBarValue).thenAccept(result -> {
229 assertFalse(result);
230 }).join();
231
232 map.clear().join();
233
234 map.size().thenAccept(result -> {
235 assertTrue(result == 0);
236 }).join();
237 }
238
239 public void mapComputeOperationTests(int clusterSize) throws Throwable {
240 createCopycatServers(clusterSize);
241 final byte[] value1 = Tools.getBytesUtf8("value1");
242 final byte[] value2 = Tools.getBytesUtf8("value2");
243 final byte[] value3 = Tools.getBytesUtf8("value3");
244
245 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
246
247 map.computeIfAbsent("foo", k -> value1).thenAccept(result -> {
248 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
249 }).join();
250
251 map.computeIfAbsent("foo", k -> value2).thenAccept(result -> {
252 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
253 }).join();
254
255 map.computeIfPresent("bar", (k, v) -> value2).thenAccept(result -> {
256 assertNull(result);
257 });
258
259 map.computeIfPresent("foo", (k, v) -> value3).thenAccept(result -> {
260 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value3));
261 }).join();
262
263 map.computeIfPresent("foo", (k, v) -> null).thenAccept(result -> {
264 assertNull(result);
265 }).join();
266
267 map.computeIf("foo", v -> v == null, (k, v) -> value1).thenAccept(result -> {
268 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
269 }).join();
270
271 map.compute("foo", (k, v) -> value2).thenAccept(result -> {
272 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value2));
273 }).join();
274 }
275
276
277 protected void mapListenerTests(int clusterSize) throws Throwable {
278 createCopycatServers(clusterSize);
279 final byte[] value1 = Tools.getBytesUtf8("value1");
280 final byte[] value2 = Tools.getBytesUtf8("value2");
281 final byte[] value3 = Tools.getBytesUtf8("value3");
282
283 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
284 TestMapEventListener listener = new TestMapEventListener();
285
286 // add listener; insert new value into map and verify an INSERT event is received.
287 map.addListener(listener).join();
288 map.put("foo", value1).join();
289 assertNotNull(listener.event());
290 assertEquals(MapEvent.Type.INSERT, listener.event().type());
291 assertTrue(Arrays.equals(value1, listener.event().newValue().value()));
292 listener.clearEvent();
293
294 // remove listener and verify listener is not notified.
295 map.removeListener(listener).join();
296 map.put("foo", value2).join();
297 assertNull(listener.event());
298
299 // add the listener back and verify UPDATE events are received correctly
300 map.addListener(listener).join();
301 map.put("foo", value3).join();
302 assertNotNull(listener.event());
303 assertEquals(MapEvent.Type.UPDATE, listener.event().type());
304 assertTrue(Arrays.equals(value3, listener.event().newValue().value()));
305 listener.clearEvent();
306
307 // perform a non-state changing operation and verify no events are received.
308 map.putIfAbsent("foo", value1).join();
309 assertNull(listener.event());
310
311 // verify REMOVE events are received correctly.
312 map.remove("foo").join();
313 assertNotNull(listener.event());
314 assertEquals(MapEvent.Type.REMOVE, listener.event().type());
315 assertTrue(Arrays.equals(value3, listener.event().oldValue().value()));
316 listener.clearEvent();
317
318 // verify compute methods also generate events.
319 map.computeIf("foo", v -> v == null, (k, v) -> value1).join();
320 assertNotNull(listener.event());
321 assertEquals(MapEvent.Type.INSERT, listener.event().type());
322 assertTrue(Arrays.equals(value1, listener.event().newValue().value()));
323 listener.clearEvent();
324
325 map.compute("foo", (k, v) -> value2).join();
326 assertNotNull(listener.event());
327 assertEquals(MapEvent.Type.UPDATE, listener.event().type());
328 assertTrue(Arrays.equals(value2, listener.event().newValue().value()));
329 listener.clearEvent();
330
331 map.computeIf("foo", v -> Arrays.equals(v, value2), (k, v) -> null).join();
332 assertNotNull(listener.event());
333 assertEquals(MapEvent.Type.REMOVE, listener.event().type());
334 assertTrue(Arrays.equals(value2, listener.event().oldValue().value()));
335 listener.clearEvent();
336
337 map.removeListener(listener).join();
338 }
339
340 protected void transactionCommitTests(int clusterSize) throws Throwable {
341 createCopycatServers(clusterSize);
342 final byte[] value1 = Tools.getBytesUtf8("value1");
343 final byte[] value2 = Tools.getBytesUtf8("value2");
344
345 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
346 TestMapEventListener listener = new TestMapEventListener();
347
348 map.addListener(listener).join();
349
350 MapUpdate<String, byte[]> update1 =
351 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
352 .withKey("foo")
353 .withValue(value1)
354 .build();
355
Madan Jampanicadd70b2016-02-08 13:45:43 -0800356 Transaction tx = new Transaction(TransactionId.from("tx1"), Arrays.asList(update1));
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800357
Madan Jampanicadd70b2016-02-08 13:45:43 -0800358 map.prepare(tx).thenAccept(result -> {
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800359 assertEquals(PrepareResult.OK, result);
360 }).join();
361 assertNull(listener.event());
362
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();
373 assertTrue(false);
374 } catch (CompletionException e) {
375 assertEquals(ConcurrentModificationException.class, e.getCause().getClass());
376 }
377
378 assertNull(listener.event());
379
Madan Jampanicadd70b2016-02-08 13:45:43 -0800380 map.commit(tx.id()).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800381 assertNotNull(listener.event());
382 assertEquals(MapEvent.Type.INSERT, listener.event().type());
383 assertTrue(Arrays.equals(value1, listener.event().newValue().value()));
384 listener.clearEvent();
385
386 map.put("foo", value2).thenAccept(result -> {
387 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
388 }).join();
389 assertNotNull(listener.event());
390 assertEquals(MapEvent.Type.UPDATE, listener.event().type());
391 assertTrue(Arrays.equals(value2, listener.event().newValue().value()));
392 listener.clearEvent();
393 }
394
395 protected void transactionRollbackTests(int clusterSize) throws Throwable {
396 createCopycatServers(clusterSize);
397 final byte[] value1 = Tools.getBytesUtf8("value1");
398 final byte[] value2 = Tools.getBytesUtf8("value2");
399
400 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
401 TestMapEventListener listener = new TestMapEventListener();
402
403 map.addListener(listener).join();
404
405 MapUpdate<String, byte[]> update1 =
406 MapUpdate.<String, byte[]>newBuilder().withType(MapUpdate.Type.PUT_IF_ABSENT)
407 .withKey("foo")
408 .withValue(value1)
409 .build();
Madan Jampanicadd70b2016-02-08 13:45:43 -0800410 Transaction tx = new Transaction(TransactionId.from("tx1"), Arrays.asList(update1));
411 map.prepare(tx).thenAccept(result -> {
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800412 assertEquals(PrepareResult.OK, result);
413 }).join();
414 assertNull(listener.event());
415
Madan Jampanicadd70b2016-02-08 13:45:43 -0800416 map.rollback(tx.id()).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800417 assertNull(listener.event());
418
419 map.get("foo").thenAccept(result -> {
420 assertNull(result);
421 }).join();
422
423 map.put("foo", value2).thenAccept(result -> {
424 assertNull(result);
425 }).join();
426 assertNotNull(listener.event());
427 assertEquals(MapEvent.Type.INSERT, listener.event().type());
428 assertTrue(Arrays.equals(value2, listener.event().newValue().value()));
429 listener.clearEvent();
430 }
431
432 private static class TestMapEventListener implements MapEventListener<String, byte[]> {
433
434 MapEvent<String, byte[]> event;
435
436 @Override
437 public void event(MapEvent<String, byte[]> event) {
438 this.event = event;
439 }
440
441 public MapEvent<String, byte[]> event() {
442 return event;
443 }
444
445 public void clearEvent() {
446 event = null;
447 }
448 }
449}