blob: aad068a1d3b2dba70a9774c360f42318df12dfba [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;
Madan Jampani630e7ac2016-05-31 11:34:05 -070023
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070024import io.atomix.catalyst.transport.Address;
Madan Jampani630e7ac2016-05-31 11:34:05 -070025import io.atomix.catalyst.transport.local.LocalTransport;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070026import io.atomix.copycat.server.CopycatServer;
27import io.atomix.copycat.server.storage.Storage;
28import io.atomix.copycat.server.storage.StorageLevel;
Madan Jampani630e7ac2016-05-31 11:34:05 -070029import io.atomix.manager.internal.ResourceManagerState;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070030import io.atomix.resource.ResourceType;
Madan Jampani630e7ac2016-05-31 11:34:05 -070031
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070032import org.apache.commons.collections.keyvalue.DefaultMapEntry;
33import org.junit.Ignore;
34import org.junit.Test;
35import org.onlab.util.Tools;
36
37import java.io.File;
38import java.time.Duration;
39import java.util.Arrays;
40import java.util.Collection;
41import java.util.Comparator;
42import java.util.List;
43import java.util.Map;
44
45import static org.junit.Assert.assertEquals;
46import static org.junit.Assert.assertFalse;
47import static org.junit.Assert.assertTrue;
48
49/**
50 * Tests the {@link AsyncConsistentSetMultimap}.
51 */
Madan Jampani9a624592016-06-03 10:14:38 -070052@Ignore
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070053public class AsyncConsistentSetMultimapTest extends AtomixTestBase {
54 private final File testDir = Files.createTempDir();
55 private final String keyOne = "hello";
56 private final String keyTwo = "goodbye";
57 private final String keyThree = "foo";
58 private final String keyFour = "bar";
59 private final byte[] valueOne = Tools.getBytesUtf8(keyOne);
60 private final byte[] valueTwo = Tools.getBytesUtf8(keyTwo);
61 private final byte[] valueThree = Tools.getBytesUtf8(keyThree);
62 private final byte[] valueFour = Tools.getBytesUtf8(keyFour);
63 private final List<String> allKeys = Lists.newArrayList(keyOne, keyTwo,
64 keyThree, keyFour);
65 private final List<byte[]> allValues = Lists.newArrayList(valueOne,
66 valueTwo,
67 valueThree,
68 valueFour);
69
70 @Override
71 protected ResourceType resourceType() {
72 return new ResourceType(AsyncConsistentSetMultimap.class);
73 }
74
75 /**
76 * Test that size behaves correctly (This includes testing of the empty
77 * check).
78 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070079 @Test
80 public void testSize() throws Throwable {
81 clearTests();
82 AsyncConsistentSetMultimap map = createResource(3);
83 //Simplest operation case
84 map.isEmpty().thenAccept(result -> assertTrue(result));
85 map.put(keyOne, valueOne).
86 thenAccept(result -> assertTrue(result)).join();
87 map.isEmpty().thenAccept(result -> assertFalse(result));
88 map.size().thenAccept(result -> assertEquals(1, (int) result))
89 .join();
90 //Make sure sizing is dependent on values not keys
91 map.put(keyOne, valueTwo).
92 thenAccept(result -> assertTrue(result)).join();
93 map.size().thenAccept(result -> assertEquals(2, (int) result))
94 .join();
95 //Ensure that double adding has no effect
96 map.put(keyOne, valueOne).
97 thenAccept(result -> assertFalse(result)).join();
98 map.size().thenAccept(result -> assertEquals(2, (int) result))
99 .join();
100 //Check handling for multiple keys
101 map.put(keyTwo, valueOne)
102 .thenAccept(result -> assertTrue(result)).join();
103 map.put(keyTwo, valueTwo)
104 .thenAccept(result -> assertTrue(result)).join();
105 map.size().thenAccept(result -> assertEquals(4, (int) result))
106 .join();
107 //Check size with removal
108 map.remove(keyOne, valueOne).
109 thenAccept(result -> assertTrue(result)).join();
110 map.size().thenAccept(result -> assertEquals(3, (int) result))
111 .join();
112 //Check behavior under remove of non-existant key
113 map.remove(keyOne, valueOne).
114 thenAccept(result -> assertFalse(result)).join();
115 map.size().thenAccept(result -> assertEquals(3, (int) result))
116 .join();
117 //Check clearing the entirety of the map
118 map.clear().join();
119 map.size().thenAccept(result -> assertEquals(0, (int) result))
120 .join();
121 map.isEmpty().thenAccept(result -> assertTrue(result));
122
123 map.destroy().join();
124 clearTests();
125 }
126
127 /**
128 * Contains tests for value, key and entry.
129 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700130 @Test
131 public void containsTest() throws Throwable {
132 clearTests();
133 AsyncConsistentSetMultimap map = createResource(3);
134
135 //Populate the maps
136 allKeys.forEach(key -> {
137 map.putAll(key, allValues)
138 .thenAccept(result -> assertTrue(result)).join();
139 });
140 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
141
142 //Test key contains positive results
143 allKeys.forEach(key -> {
144 map.containsKey(key)
145 .thenAccept(result -> assertTrue(result)).join();
146 });
147
148 //Test value contains positive results
149 allValues.forEach(value -> {
150 map.containsValue(value)
151 .thenAccept(result -> assertTrue(result)).join();
152 });
153
154 //Test contains entry for all possible entries
155 allKeys.forEach(key -> {
156 allValues.forEach(value -> {
157 map.containsEntry(key, value)
158 .thenAccept(result -> assertTrue(result)).join();
159 });
160 });
161
162 //Test behavior after removals
163 allValues.forEach(value -> {
164 final String[] removedKey = new String[1];
165 allKeys.forEach(key -> {
166 map.remove(key, value)
167 .thenAccept(result -> assertTrue(result)).join();
168 map.containsEntry(key, value)
169 .thenAccept(result -> assertFalse(result)).join();
170 removedKey[0] = key;
171 });
172 //Check that contains key works properly for removed keys
173 map.containsKey(removedKey[0])
174 .thenAccept(result -> assertFalse(result));
175 });
176
177 //Check that contains value works correctly for removed values
178 allValues.forEach(value -> {
179 map.containsValue(value)
180 .thenAccept(result -> assertFalse(result)).join();
181 });
182
183 map.destroy().join();
184 clearTests();
185 }
186
187 /**
188 * Contains tests for put, putAll, remove, removeAll and replace.
189 * @throws Exception
190 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700191 @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
alshabibef10b732016-05-26 16:10:08 -0700324 * as a trivial test of the asMap functionality (throws error).
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700325 * @throws Exception
326 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700327 @Test
328 public void testAccessors() throws Exception {
329 clearTests();
330 AsyncConsistentSetMultimap map = createResource(3);
331
332 //Populate for full map behavior tests
333 allKeys.forEach(key -> {
334 map.putAll(key, allValues)
335 .thenAccept(result -> assertTrue(result)).join();
336 });
337
338 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
339
340 allKeys.forEach(key -> {
341 map.get(key).thenAccept(result -> {
342 assertTrue(byteArrayCollectionIsEqual(allValues,
343 result.value()));
344 }).join();
345 });
346
347 //Test that the key set is correct
348 map.keySet()
349 .thenAccept(result ->
350 assertTrue(stringArrayCollectionIsEqual(allKeys,
351 result)))
352 .join();
353 //Test that the correct set and occurrence of values are found in the
354 //values result
355 map.values().thenAccept(result -> {
356 final Multiset<byte[]> set = TreeMultiset.create(
357 new ByteArrayComparator());
358 for (int i = 0; i < 4; i++) {
359 set.addAll(allValues);
360 }
361 assertEquals(16, result.size());
362 result.forEach(value -> assertTrue(set.remove(value)));
363 assertTrue(set.isEmpty());
364
365 }).join();
366
367 //Test that keys returns the right result including the correct number
368 //of each item
369 map.keys().thenAccept(result -> {
370 final Multiset<String> set = TreeMultiset.create();
371 for (int i = 0; i < 4; i++) {
372 set.addAll(allKeys);
373 }
374 assertEquals(16, result.size());
375 result.forEach(value -> assertTrue(set.remove(value)));
376 assertTrue(set.isEmpty());
377
378 }).join();
379
380 //Test that the right combination of key, value pairs are present
381 map.entries().thenAccept(result -> {
382 final Multiset<Map.Entry<String, byte[]>> set =
383 TreeMultiset.create(new EntryComparator());
384 allKeys.forEach(key -> {
385 allValues.forEach(value -> {
386 set.add(new DefaultMapEntry(key, value));
387 });
388 });
389 assertEquals(16, result.size());
390 result.forEach(entry -> assertTrue(set.remove(entry)));
391 assertTrue(set.isEmpty());
392 }).join();
393
394
395 //Testing for empty map behavior
396 map.clear().join();
397
398 allKeys.forEach(key -> {
399 map.get(key).thenAccept(result -> {
400 assertTrue(result.value().isEmpty());
401 }).join();
402 });
403
404 map.keySet().thenAccept(result -> assertTrue(result.isEmpty())).join();
405 map.values().thenAccept(result -> assertTrue(result.isEmpty())).join();
406 map.keys().thenAccept(result -> assertTrue(result.isEmpty())).join();
407 map.entries()
408 .thenAccept(result -> assertTrue(result.isEmpty())).join();
409
410 map.destroy();
411 clearTests();
412 }
413
414
415 private AsyncConsistentSetMultimap createResource(int clusterSize) {
416 try {
417 createCopycatServers(clusterSize);
418 AsyncConsistentSetMultimap map = createAtomixClient().
419 getResource("testMap", AsyncConsistentSetMultimap.class)
420 .join();
421 return map;
422 } catch (Throwable e) {
423 throw new RuntimeException(e.toString());
424 }
425 }
426
427 @Override
428 protected CopycatServer createCopycatServer(Address address) {
Madan Jampani630e7ac2016-05-31 11:34:05 -0700429 CopycatServer server = CopycatServer.builder(address)
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700430 .withTransport(new LocalTransport(registry))
431 .withStorage(Storage.builder()
432 .withStorageLevel(StorageLevel.MEMORY)
433 .withDirectory(testDir + "/" + address.port())
434 .build())
435 .withStateMachine(ResourceManagerState::new)
436 .withSerializer(serializer.clone())
437 .withHeartbeatInterval(Duration.ofMillis(25))
438 .withElectionTimeout(Duration.ofMillis(50))
439 .withSessionTimeout(Duration.ofMillis(100))
440 .build();
441 copycatServers.add(server);
Madan Jampani630e7ac2016-05-31 11:34:05 -0700442 return server;
443 }
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700444
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}