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