blob: 26aa9a8e855b16ebe61f9fda1b469f2339752a63 [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 Jampani74da78b2016-02-09 21:18:36 -080030import org.onosproject.store.primitives.MapUpdate;
Madan Jampanicadd70b2016-02-08 13:45:43 -080031import org.onosproject.store.primitives.TransactionId;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080032import org.onosproject.store.service.MapEvent;
33import org.onosproject.store.service.MapEventListener;
Madan Jampani74da78b2016-02-09 21:18:36 -080034import org.onosproject.store.service.MapTransaction;
Madan Jampani5e5b3d62016-02-01 16:03:33 -080035import org.onosproject.store.service.Versioned;
36
37import com.google.common.collect.Sets;
38
39/**
40 * Unit tests for {@link AtomixConsistentMap}.
41 */
Madan Jampani1f0202c2016-02-03 14:30:29 -080042@Ignore
Madan Jampani5e5b3d62016-02-01 16:03:33 -080043public class AtomixConsistentMapTest extends AtomixTestBase {
44
45 @Override
46 protected ResourceType resourceType() {
47 return new ResourceType(AtomixConsistentMap.class);
48 }
49
50 /**
51 * Tests various basic map operations.
52 */
53 @Test
54 public void testBasicMapOperations() throws Throwable {
55 basicMapOperationTests(1);
56 clearTests();
57 basicMapOperationTests(2);
58 clearTests();
59 basicMapOperationTests(3);
60 }
61
62 /**
63 * Tests various map compute* operations on different cluster sizes.
64 */
65 @Test
66 public void testMapComputeOperations() throws Throwable {
67 mapComputeOperationTests(1);
68 clearTests();
69 mapComputeOperationTests(2);
70 clearTests();
71 mapComputeOperationTests(3);
72 }
73
74 /**
75 * Tests map event notifications.
76 */
77 @Test
78 public void testMapListeners() throws Throwable {
79 mapListenerTests(1);
80 clearTests();
81 mapListenerTests(2);
82 clearTests();
83 mapListenerTests(3);
84 }
85
86 /**
87 * Tests map transaction commit.
88 */
89 @Test
90 public void testTransactionCommit() throws Throwable {
91 transactionCommitTests(1);
92 clearTests();
93 transactionCommitTests(2);
94 clearTests();
95 transactionCommitTests(3);
96 }
97
98 /**
99 * Tests map transaction rollback.
100 */
101 @Test
102 public void testTransactionRollback() throws Throwable {
103 transactionRollbackTests(1);
104 clearTests();
105 transactionRollbackTests(2);
106 clearTests();
107 transactionRollbackTests(3);
108 }
109
110 protected void basicMapOperationTests(int clusterSize) throws Throwable {
111 createCopycatServers(clusterSize);
112
113 final byte[] rawFooValue = Tools.getBytesUtf8("Hello foo!");
114 final byte[] rawBarValue = Tools.getBytesUtf8("Hello bar!");
115
116 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
117
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 {
241 createCopycatServers(clusterSize);
242 final byte[] value1 = Tools.getBytesUtf8("value1");
243 final byte[] value2 = Tools.getBytesUtf8("value2");
244 final byte[] value3 = Tools.getBytesUtf8("value3");
245
246 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
247
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 {
279 createCopycatServers(clusterSize);
280 final byte[] value1 = Tools.getBytesUtf8("value1");
281 final byte[] value2 = Tools.getBytesUtf8("value2");
282 final byte[] value3 = Tools.getBytesUtf8("value3");
283
284 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
285 TestMapEventListener listener = new TestMapEventListener();
286
287 // add listener; insert new value into map and verify an INSERT event is received.
288 map.addListener(listener).join();
289 map.put("foo", value1).join();
290 assertNotNull(listener.event());
291 assertEquals(MapEvent.Type.INSERT, listener.event().type());
292 assertTrue(Arrays.equals(value1, listener.event().newValue().value()));
293 listener.clearEvent();
294
295 // remove listener and verify listener is not notified.
296 map.removeListener(listener).join();
297 map.put("foo", value2).join();
298 assertNull(listener.event());
299
300 // add the listener back and verify UPDATE events are received correctly
301 map.addListener(listener).join();
302 map.put("foo", value3).join();
303 assertNotNull(listener.event());
304 assertEquals(MapEvent.Type.UPDATE, listener.event().type());
305 assertTrue(Arrays.equals(value3, listener.event().newValue().value()));
306 listener.clearEvent();
307
308 // perform a non-state changing operation and verify no events are received.
309 map.putIfAbsent("foo", value1).join();
310 assertNull(listener.event());
311
312 // verify REMOVE events are received correctly.
313 map.remove("foo").join();
314 assertNotNull(listener.event());
315 assertEquals(MapEvent.Type.REMOVE, listener.event().type());
316 assertTrue(Arrays.equals(value3, listener.event().oldValue().value()));
317 listener.clearEvent();
318
319 // verify compute methods also generate events.
320 map.computeIf("foo", v -> v == null, (k, v) -> value1).join();
321 assertNotNull(listener.event());
322 assertEquals(MapEvent.Type.INSERT, listener.event().type());
323 assertTrue(Arrays.equals(value1, listener.event().newValue().value()));
324 listener.clearEvent();
325
326 map.compute("foo", (k, v) -> value2).join();
327 assertNotNull(listener.event());
328 assertEquals(MapEvent.Type.UPDATE, listener.event().type());
329 assertTrue(Arrays.equals(value2, listener.event().newValue().value()));
330 listener.clearEvent();
331
332 map.computeIf("foo", v -> Arrays.equals(v, value2), (k, v) -> null).join();
333 assertNotNull(listener.event());
334 assertEquals(MapEvent.Type.REMOVE, listener.event().type());
335 assertTrue(Arrays.equals(value2, listener.event().oldValue().value()));
336 listener.clearEvent();
337
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
346 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
347 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();
362 assertNull(listener.event());
363
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
379 assertNull(listener.event());
380
Madan Jampani74da78b2016-02-09 21:18:36 -0800381 map.commit(tx.transactionId()).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800382 assertNotNull(listener.event());
383 assertEquals(MapEvent.Type.INSERT, listener.event().type());
384 assertTrue(Arrays.equals(value1, listener.event().newValue().value()));
385 listener.clearEvent();
386
387 map.put("foo", value2).thenAccept(result -> {
388 assertTrue(Arrays.equals(Versioned.valueOrElse(result, null), value1));
389 }).join();
390 assertNotNull(listener.event());
391 assertEquals(MapEvent.Type.UPDATE, listener.event().type());
392 assertTrue(Arrays.equals(value2, listener.event().newValue().value()));
393 listener.clearEvent();
394 }
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
401 AtomixConsistentMap map = createAtomixClient().get("test", AtomixConsistentMap.class).join();
402 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();
415 assertNull(listener.event());
416
Madan Jampani74da78b2016-02-09 21:18:36 -0800417 map.rollback(tx.transactionId()).join();
Madan Jampani5e5b3d62016-02-01 16:03:33 -0800418 assertNull(listener.event());
419
420 map.get("foo").thenAccept(result -> {
421 assertNull(result);
422 }).join();
423
424 map.put("foo", value2).thenAccept(result -> {
425 assertNull(result);
426 }).join();
427 assertNotNull(listener.event());
428 assertEquals(MapEvent.Type.INSERT, listener.event().type());
429 assertTrue(Arrays.equals(value2, listener.event().newValue().value()));
430 listener.clearEvent();
431 }
432
433 private static class TestMapEventListener implements MapEventListener<String, byte[]> {
434
435 MapEvent<String, byte[]> event;
436
437 @Override
438 public void event(MapEvent<String, byte[]> event) {
439 this.event = event;
440 }
441
442 public MapEvent<String, byte[]> event() {
443 return event;
444 }
445
446 public void clearEvent() {
447 event = null;
448 }
449 }
450}