blob: 313b098a17e0526ec81a727fe400ea87b30dc3c0 [file] [log] [blame]
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -07001/*
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 */
16
17package org.onosproject.store.primitives.resources.impl;
18
19import com.google.common.collect.Lists;
20import com.google.common.collect.Multiset;
21import com.google.common.collect.TreeMultiset;
22import com.google.common.io.Files;
23import io.atomix.catalyst.transport.Address;
24import io.atomix.catalyst.transport.LocalTransport;
25import io.atomix.copycat.server.CopycatServer;
26import io.atomix.copycat.server.storage.Storage;
27import io.atomix.copycat.server.storage.StorageLevel;
28import io.atomix.manager.state.ResourceManagerState;
29import io.atomix.resource.ResourceType;
30import org.apache.commons.collections.keyvalue.DefaultMapEntry;
31import org.junit.Ignore;
32import org.junit.Test;
33import org.onlab.util.Tools;
34
35import java.io.File;
36import java.time.Duration;
37import java.util.Arrays;
38import java.util.Collection;
39import java.util.Comparator;
40import java.util.List;
41import java.util.Map;
42
43import static org.junit.Assert.assertEquals;
44import static org.junit.Assert.assertFalse;
45import static org.junit.Assert.assertTrue;
46
47/**
48 * Tests the {@link AsyncConsistentSetMultimap}.
49 */
50public class AsyncConsistentSetMultimapTest extends AtomixTestBase {
51 private final File testDir = Files.createTempDir();
52 private final String keyOne = "hello";
53 private final String keyTwo = "goodbye";
54 private final String keyThree = "foo";
55 private final String keyFour = "bar";
56 private final byte[] valueOne = Tools.getBytesUtf8(keyOne);
57 private final byte[] valueTwo = Tools.getBytesUtf8(keyTwo);
58 private final byte[] valueThree = Tools.getBytesUtf8(keyThree);
59 private final byte[] valueFour = Tools.getBytesUtf8(keyFour);
60 private final List<String> allKeys = Lists.newArrayList(keyOne, keyTwo,
61 keyThree, keyFour);
62 private final List<byte[]> allValues = Lists.newArrayList(valueOne,
63 valueTwo,
64 valueThree,
65 valueFour);
66
67 @Override
68 protected ResourceType resourceType() {
69 return new ResourceType(AsyncConsistentSetMultimap.class);
70 }
71
72 /**
73 * Test that size behaves correctly (This includes testing of the empty
74 * check).
75 */
76 @Ignore
77 @Test
78 public void testSize() throws Throwable {
79 clearTests();
80 AsyncConsistentSetMultimap map = createResource(3);
81 //Simplest operation case
82 map.isEmpty().thenAccept(result -> assertTrue(result));
83 map.put(keyOne, valueOne).
84 thenAccept(result -> assertTrue(result)).join();
85 map.isEmpty().thenAccept(result -> assertFalse(result));
86 map.size().thenAccept(result -> assertEquals(1, (int) result))
87 .join();
88 //Make sure sizing is dependent on values not keys
89 map.put(keyOne, valueTwo).
90 thenAccept(result -> assertTrue(result)).join();
91 map.size().thenAccept(result -> assertEquals(2, (int) result))
92 .join();
93 //Ensure that double adding has no effect
94 map.put(keyOne, valueOne).
95 thenAccept(result -> assertFalse(result)).join();
96 map.size().thenAccept(result -> assertEquals(2, (int) result))
97 .join();
98 //Check handling for multiple keys
99 map.put(keyTwo, valueOne)
100 .thenAccept(result -> assertTrue(result)).join();
101 map.put(keyTwo, valueTwo)
102 .thenAccept(result -> assertTrue(result)).join();
103 map.size().thenAccept(result -> assertEquals(4, (int) result))
104 .join();
105 //Check size with removal
106 map.remove(keyOne, valueOne).
107 thenAccept(result -> assertTrue(result)).join();
108 map.size().thenAccept(result -> assertEquals(3, (int) result))
109 .join();
110 //Check behavior under remove of non-existant key
111 map.remove(keyOne, valueOne).
112 thenAccept(result -> assertFalse(result)).join();
113 map.size().thenAccept(result -> assertEquals(3, (int) result))
114 .join();
115 //Check clearing the entirety of the map
116 map.clear().join();
117 map.size().thenAccept(result -> assertEquals(0, (int) result))
118 .join();
119 map.isEmpty().thenAccept(result -> assertTrue(result));
120
121 map.destroy().join();
122 clearTests();
123 }
124
125 /**
126 * Contains tests for value, key and entry.
127 */
128 @Ignore
129 @Test
130 public void containsTest() throws Throwable {
131 clearTests();
132 AsyncConsistentSetMultimap map = createResource(3);
133
134 //Populate the maps
135 allKeys.forEach(key -> {
136 map.putAll(key, allValues)
137 .thenAccept(result -> assertTrue(result)).join();
138 });
139 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
140
141 //Test key contains positive results
142 allKeys.forEach(key -> {
143 map.containsKey(key)
144 .thenAccept(result -> assertTrue(result)).join();
145 });
146
147 //Test value contains positive results
148 allValues.forEach(value -> {
149 map.containsValue(value)
150 .thenAccept(result -> assertTrue(result)).join();
151 });
152
153 //Test contains entry for all possible entries
154 allKeys.forEach(key -> {
155 allValues.forEach(value -> {
156 map.containsEntry(key, value)
157 .thenAccept(result -> assertTrue(result)).join();
158 });
159 });
160
161 //Test behavior after removals
162 allValues.forEach(value -> {
163 final String[] removedKey = new String[1];
164 allKeys.forEach(key -> {
165 map.remove(key, value)
166 .thenAccept(result -> assertTrue(result)).join();
167 map.containsEntry(key, value)
168 .thenAccept(result -> assertFalse(result)).join();
169 removedKey[0] = key;
170 });
171 //Check that contains key works properly for removed keys
172 map.containsKey(removedKey[0])
173 .thenAccept(result -> assertFalse(result));
174 });
175
176 //Check that contains value works correctly for removed values
177 allValues.forEach(value -> {
178 map.containsValue(value)
179 .thenAccept(result -> assertFalse(result)).join();
180 });
181
182 map.destroy().join();
183 clearTests();
184 }
185
186 /**
187 * Contains tests for put, putAll, remove, removeAll and replace.
188 * @throws Exception
189 */
190 @Ignore
191 @Test
192 public void addAndRemoveTest() throws Exception {
193 clearTests();
194 AsyncConsistentSetMultimap map = createResource(3);
195
196 //Test single put
197 allKeys.forEach(key -> {
198 //Value should actually be added here
199 allValues.forEach(value -> {
200 map.put(key, value)
201 .thenAccept(result -> assertTrue(result)).join();
202 //Duplicate values should be ignored here
203 map.put(key, value)
204 .thenAccept(result -> assertFalse(result)).join();
205 });
206 });
207
208 //Test single remove
209 allKeys.forEach(key -> {
210 //Value should actually be added here
211 allValues.forEach(value -> {
212 map.remove(key, value)
213 .thenAccept(result -> assertTrue(result)).join();
214 //Duplicate values should be ignored here
215 map.remove(key, value)
216 .thenAccept(result -> assertFalse(result)).join();
217 });
218 });
219
220 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
221
222 //Test multi put
223 allKeys.forEach(key -> {
224 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
225 .thenAccept(result -> assertTrue(result)).join();
226 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
227 .thenAccept(result -> assertFalse(result)).join();
228 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
229 .thenAccept(result -> assertTrue(result)).join();
230 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
231 .thenAccept(result -> assertFalse(result)).join();
232
233 });
234
235 //Test multi remove
236 allKeys.forEach(key -> {
237 //Split the lists to test how multiRemove can work piecewise
238 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
239 .thenAccept(result -> assertTrue(result)).join();
240 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
241 .thenAccept(result -> assertFalse(result)).join();
242 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
243 .thenAccept(result -> assertTrue(result)).join();
244 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
245 .thenAccept(result -> assertFalse(result)).join();
246 });
247
248 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
249
250 //Repopulate for next test
251 allKeys.forEach(key -> {
252 map.putAll(key, allValues)
253 .thenAccept(result -> assertTrue(result)).join();
254 });
255
256 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
257
258 //Test removeAll of entire entry
259 allKeys.forEach(key -> {
260 map.removeAll(key).thenAccept(result -> {
261 assertTrue(
262 byteArrayCollectionIsEqual(allValues, result.value()));
263 }).join();
264 map.removeAll(key).thenAccept(result -> {
265 assertFalse(
266 byteArrayCollectionIsEqual(allValues, result.value()));
267 }).join();
268 });
269
270 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
271
272 //Repopulate for next test
273 allKeys.forEach(key -> {
274 map.putAll(key, allValues)
275 .thenAccept(result -> assertTrue(result)).join();
276 });
277
278 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
279
280 allKeys.forEach(key -> {
281 map.replaceValues(key, allValues)
282 .thenAccept(result ->
283 assertTrue(byteArrayCollectionIsEqual(allValues,
284 result.value())))
285 .join();
286 map.replaceValues(key, Lists.newArrayList())
287 .thenAccept(result ->
288 assertTrue(byteArrayCollectionIsEqual(allValues,
289 result.value())))
290 .join();
291 map.replaceValues(key, allValues)
292 .thenAccept(result ->
293 assertTrue(result.value().isEmpty()))
294 .join();
295 });
296
297
298 //Test replacements of partial sets
299 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
300
301 allKeys.forEach(key -> {
302 map.remove(key, valueOne)
303 .thenAccept(result ->
304 assertTrue(result)).join();
305 map.replaceValues(key, Lists.newArrayList())
306 .thenAccept(result ->
307 assertTrue(byteArrayCollectionIsEqual(
308 Lists.newArrayList(valueTwo, valueThree,
309 valueFour),
310 result.value())))
311 .join();
312 map.replaceValues(key, allValues)
313 .thenAccept(result ->
314 assertTrue(result.value().isEmpty()))
315 .join();
316 });
317
318 map.destroy().join();
319 clearTests();
320 }
321
322 /**
323 * Tests the get, keySet, keys, values, and entries implementations as well
324 * as a trivial test of the asMap functionality (throws error)
325 * @throws Exception
326 */
327 @Ignore
328 @Test
329 public void testAccessors() throws Exception {
330 clearTests();
331 AsyncConsistentSetMultimap map = createResource(3);
332
333 //Populate for full map behavior tests
334 allKeys.forEach(key -> {
335 map.putAll(key, allValues)
336 .thenAccept(result -> assertTrue(result)).join();
337 });
338
339 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
340
341 allKeys.forEach(key -> {
342 map.get(key).thenAccept(result -> {
343 assertTrue(byteArrayCollectionIsEqual(allValues,
344 result.value()));
345 }).join();
346 });
347
348 //Test that the key set is correct
349 map.keySet()
350 .thenAccept(result ->
351 assertTrue(stringArrayCollectionIsEqual(allKeys,
352 result)))
353 .join();
354 //Test that the correct set and occurrence of values are found in the
355 //values result
356 map.values().thenAccept(result -> {
357 final Multiset<byte[]> set = TreeMultiset.create(
358 new ByteArrayComparator());
359 for (int i = 0; i < 4; i++) {
360 set.addAll(allValues);
361 }
362 assertEquals(16, result.size());
363 result.forEach(value -> assertTrue(set.remove(value)));
364 assertTrue(set.isEmpty());
365
366 }).join();
367
368 //Test that keys returns the right result including the correct number
369 //of each item
370 map.keys().thenAccept(result -> {
371 final Multiset<String> set = TreeMultiset.create();
372 for (int i = 0; i < 4; i++) {
373 set.addAll(allKeys);
374 }
375 assertEquals(16, result.size());
376 result.forEach(value -> assertTrue(set.remove(value)));
377 assertTrue(set.isEmpty());
378
379 }).join();
380
381 //Test that the right combination of key, value pairs are present
382 map.entries().thenAccept(result -> {
383 final Multiset<Map.Entry<String, byte[]>> set =
384 TreeMultiset.create(new EntryComparator());
385 allKeys.forEach(key -> {
386 allValues.forEach(value -> {
387 set.add(new DefaultMapEntry(key, value));
388 });
389 });
390 assertEquals(16, result.size());
391 result.forEach(entry -> assertTrue(set.remove(entry)));
392 assertTrue(set.isEmpty());
393 }).join();
394
395
396 //Testing for empty map behavior
397 map.clear().join();
398
399 allKeys.forEach(key -> {
400 map.get(key).thenAccept(result -> {
401 assertTrue(result.value().isEmpty());
402 }).join();
403 });
404
405 map.keySet().thenAccept(result -> assertTrue(result.isEmpty())).join();
406 map.values().thenAccept(result -> assertTrue(result.isEmpty())).join();
407 map.keys().thenAccept(result -> assertTrue(result.isEmpty())).join();
408 map.entries()
409 .thenAccept(result -> assertTrue(result.isEmpty())).join();
410
411 map.destroy();
412 clearTests();
413 }
414
415
416 private AsyncConsistentSetMultimap createResource(int clusterSize) {
417 try {
418 createCopycatServers(clusterSize);
419 AsyncConsistentSetMultimap map = createAtomixClient().
420 getResource("testMap", AsyncConsistentSetMultimap.class)
421 .join();
422 return map;
423 } catch (Throwable e) {
424 throw new RuntimeException(e.toString());
425 }
426 }
427
428 @Override
429 protected CopycatServer createCopycatServer(Address address) {
430 CopycatServer server = CopycatServer.builder(address, members)
431 .withTransport(new LocalTransport(registry))
432 .withStorage(Storage.builder()
433 .withStorageLevel(StorageLevel.MEMORY)
434 .withDirectory(testDir + "/" + address.port())
435 .build())
436 .withStateMachine(ResourceManagerState::new)
437 .withSerializer(serializer.clone())
438 .withHeartbeatInterval(Duration.ofMillis(25))
439 .withElectionTimeout(Duration.ofMillis(50))
440 .withSessionTimeout(Duration.ofMillis(100))
441 .build();
442 copycatServers.add(server);
443 return server; }
444
445 /**
446 * Returns two arrays contain the same set of elements,
447 * regardless of order.
448 * @param o1 first collection
449 * @param o2 second collection
450 * @return true if they contain the same elements
451 */
452 private boolean byteArrayCollectionIsEqual(
453 Collection<? extends byte[]> o1, Collection<? extends byte[]> o2) {
454 if (o1 == null || o2 == null || o1.size() != o2.size()) {
455 return false;
456 }
457 for (byte[] array1 : o1) {
458 boolean matched = false;
459 for (byte[] array2 : o2) {
460 if (Arrays.equals(array1, array2)) {
461 matched = true;
462 break;
463 }
464 }
465 if (!matched) {
466 return false;
467 }
468 }
469 return true;
470 }
471
472 /**
473 * Compares two collections of strings returns true if they contain the
474 * same strings, false otherwise.
475 * @param s1 string collection one
476 * @param s2 string collection two
477 * @return true if the two sets contain the same strings
478 */
479 private boolean stringArrayCollectionIsEqual(
480 Collection<? extends String> s1, Collection<? extends String> s2) {
481 if (s1 == null || s2 == null || s1.size() != s2.size()) {
482 return false;
483 }
484 for (String string1 : s1) {
485 boolean matched = false;
486 for (String string2 : s2) {
487 if (string1.equals(string2)) {
488 matched = true;
489 break;
490 }
491 }
492 if (!matched) {
493 return false;
494 }
495 }
496 return true;
497 }
498
499 /**
500 * Byte array comparator implementation.
501 */
502 private class ByteArrayComparator implements Comparator<byte[]> {
503
504 @Override
505 public int compare(byte[] o1, byte[] o2) {
506 if (Arrays.equals(o1, o2)) {
507 return 0;
508 } else {
509 for (int i = 0; i < o1.length && i < o2.length; i++) {
510 if (o1[i] < o2[i]) {
511 return -1;
512 } else if (o1[i] > o2[i]) {
513 return 1;
514 }
515 }
516 return o1.length > o2.length ? 1 : -1;
517 }
518 }
519 }
520
521 /**
522 * Entry comparator, uses both key and value to determine equality,
523 * for comparison falls back to the default string comparator.
524 */
525 private class EntryComparator
526 implements Comparator<Map.Entry<String, byte[]>> {
527
528 @Override
529 public int compare(Map.Entry<String, byte[]> o1,
530 Map.Entry<String, byte[]> o2) {
531 if (o1 == null || o1.getKey() == null || o2 == null ||
532 o2.getKey() == null) {
533 throw new IllegalArgumentException();
534 }
535 if (o1.getKey().equals(o2.getKey()) &&
536 Arrays.equals(o1.getValue(), o2.getValue())) {
537 return 0;
538 } else {
539 return o1.getKey().compareTo(o2.getKey());
540 }
541 }
542 }
543}