[AETHER-72] Add bulk update to ConsistentMultimap
Change-Id: I61e9f0c2ed5ab368777c64b6fb4aa2c8dd31d081
diff --git a/core/api/src/main/java/org/onosproject/store/primitives/DefaultConsistentMultimap.java b/core/api/src/main/java/org/onosproject/store/primitives/DefaultConsistentMultimap.java
index c14b187..51f0934 100644
--- a/core/api/src/main/java/org/onosproject/store/primitives/DefaultConsistentMultimap.java
+++ b/core/api/src/main/java/org/onosproject/store/primitives/DefaultConsistentMultimap.java
@@ -111,11 +111,21 @@
}
@Override
+ public boolean removeAll(Map<K, Collection<? extends V>> mapping) {
+ return complete(asyncMultimap.removeAll(mapping));
+ }
+
+ @Override
public boolean putAll(K key, Collection<? extends V> values) {
return complete(asyncMultimap.putAll(key, values));
}
@Override
+ public boolean putAll(Map<K, Collection<? extends V>> mapping) {
+ return complete(asyncMultimap.putAll(mapping));
+ }
+
+ @Override
public Versioned<Collection<? extends V>> replaceValues(
K key, Collection<V> values) {
return complete(asyncMultimap.replaceValues(key, values));
diff --git a/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMultimap.java b/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMultimap.java
index 94943b8..b86845e 100644
--- a/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMultimap.java
+++ b/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMultimap.java
@@ -166,6 +166,19 @@
CompletableFuture<Versioned<Collection<? extends V>>> removeAll(K key);
/**
+ * Removes the set of key-value pairs with the specified key and values if they
+ * exist. In implementations that allow duplicates each instance of a key
+ * will remove one matching entry, which one is not defined. Equivalent to
+ * repeated calls to {@code remove()} for each key value pair but more
+ * efficient.
+ *
+ * @param mapping the keys-values to be removed
+ * @return a future whose value will be true if the map changes because of
+ * this call, false otherwise.
+ */
+ CompletableFuture<Boolean> removeAll(Map<K, Collection<? extends V>> mapping);
+
+ /**
* Adds the set of key-value pairs of the specified key with each of the
* values in the iterable if each key-value pair does not already exist,
* if the pair does exist the behavior is implementation specific.
@@ -180,6 +193,18 @@
Collection<? extends V> values);
/**
+ * Adds the set of key-value pairs of the specified mapping with each of
+ * the values in the iterable if each key-value pair does not already exist,
+ * if the pair does exist the behavior is implementation specific.
+ * (Same as repeated puts but with efficiency gains.)
+ *
+ * @param mapping the keys-values to be added
+ * @return a future whose value will be true if any change in the map
+ * results from this call, false otherwise
+ */
+ CompletableFuture<Boolean> putAll(Map<K, Collection<? extends V>> mapping);
+
+ /**
* Stores all the values in values associated with the key specified,
* removes all preexisting values and returns a collection of the removed
* values which may be empty if the entry did not exist.
diff --git a/core/api/src/main/java/org/onosproject/store/service/ConsistentMultimap.java b/core/api/src/main/java/org/onosproject/store/service/ConsistentMultimap.java
index 99045a8..8aabca7 100644
--- a/core/api/src/main/java/org/onosproject/store/service/ConsistentMultimap.java
+++ b/core/api/src/main/java/org/onosproject/store/service/ConsistentMultimap.java
@@ -149,6 +149,18 @@
Versioned<Collection<? extends V>> removeAll(K key);
/**
+ * Removes the set of key-value pairs with the specified key and values if they
+ * exist. In implementations that allow duplicates each instance of a key
+ * will remove one matching entry, which one is not defined. Equivalent to
+ * repeated calls to {@code remove()} for each key value pair but more
+ * efficient.
+ *
+ * @param mapping the keys-values to be removed
+ * @return true if the map changes because of this call, false otherwise.
+ */
+ boolean removeAll(Map<K, Collection<? extends V>> mapping);
+
+ /**
* Adds the set of key-value pairs of the specified key with each of the
* values in the iterable if each key-value pair does not already exist,
* if the pair does exist the behavior is implementation specific.
@@ -162,6 +174,18 @@
boolean putAll(K key, Collection<? extends V> values);
/**
+ * Adds the set of key-value pairs of the specified mapping with each of
+ * the values in the iterable if each key-value pair does not already exist,
+ * if the pair does exist the behavior is implementation specific.
+ * (Same as repeated puts but with efficiency gains.)
+ *
+ * @param mapping the keys-values to be added
+ * @return true if any change in the map results from this call,
+ * false otherwise
+ */
+ boolean putAll(Map<K, Collection<? extends V>> mapping);
+
+ /**
* Stores all the values in values associated with the key specified,
* removes all preexisting values and returns a collection of the removed
* values which may be empty if the entry did not exist.
diff --git a/core/api/src/test/java/org/onosproject/store/primitives/DefaultConsistentMultimapTest.java b/core/api/src/test/java/org/onosproject/store/primitives/DefaultConsistentMultimapTest.java
new file mode 100644
index 0000000..61a175a
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/store/primitives/DefaultConsistentMultimapTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.store.primitives;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import org.junit.Test;
+import org.onosproject.store.service.AsyncConsistentMultimapAdapter;
+import org.onosproject.store.service.ConsistentMultimap;
+import org.onosproject.store.service.Versioned;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for DefaultConsistentMultiMap.
+ */
+public class DefaultConsistentMultimapTest {
+
+ private static final String KEY1 = "AAA";
+ private static final String VALUE1 = "111";
+ private static final String KEY2 = "BBB";
+ private static final String VALUE2 = "222";
+ private static final String KEY3 = "CCC";
+ private static final String VALUE3 = "333";
+ private static final String KEY4 = "DDD";
+ private static final String VALUE4 = "444";
+ private final List<String> allKeys = Lists.newArrayList(KEY1, KEY2,
+ KEY3, KEY4);
+ private final List<String> allValues = Lists.newArrayList(VALUE1, VALUE2,
+ VALUE3, VALUE4);
+
+ /**
+ * Tests the behavior of public APIs of the default consistent multi-map
+ * implementation.
+ */
+ @Test
+ public void testBehavior() {
+ // Initialize the map
+ Multimap<String, String> baseMap = HashMultimap.create();
+ AsyncConsistentMultimapMock<String, String> asyncMultiMap = new AsyncConsistentMultimapMock<>(baseMap);
+ ConsistentMultimap<String, String> newMap = new DefaultConsistentMultimap<>(asyncMultiMap, 69);
+
+ // Verify is empty
+ assertThat(newMap.size(), is(0));
+ assertThat(newMap.isEmpty(), is(true));
+
+ // Test multi put
+ Map<String, Collection<? extends String>> mapping = Maps.newHashMap();
+ // First build the mappings having each key a different mapping
+ allKeys.forEach(key -> {
+ switch (key) {
+ case KEY1:
+ mapping.put(key, Lists.newArrayList(allValues.subList(0, 1)));
+ break;
+ case KEY2:
+ mapping.put(key, Lists.newArrayList(allValues.subList(0, 2)));
+ break;
+ case KEY3:
+ mapping.put(key, Lists.newArrayList(allValues.subList(0, 3)));
+ break;
+ default:
+ mapping.put(key, Lists.newArrayList(allValues.subList(0, 4)));
+ break;
+ }
+ });
+ // Success
+ assertThat(newMap.putAll(mapping), is(true));
+ // Failure
+ assertThat(newMap.putAll(mapping), is(false));
+ // Verify operation
+ assertThat(newMap.size(), is(10));
+ assertThat(newMap.isEmpty(), is(false));
+ // verify mapping is ok
+
+ allKeys.forEach(key -> {
+ List<String> actual = Lists.newArrayList(Versioned.valueOrNull(newMap.get(key)));
+ switch (key) {
+ case KEY1:
+ assertThat(actual, containsInAnyOrder(allValues.subList(0, 1).toArray()));
+ break;
+ case KEY2:
+ assertThat(actual, containsInAnyOrder(allValues.subList(0, 2).toArray()));
+ break;
+ case KEY3:
+ assertThat(actual, containsInAnyOrder(allValues.subList(0, 3).toArray()));
+ break;
+ default:
+ assertThat(actual, containsInAnyOrder(allValues.subList(0, 4).toArray()));
+ break;
+ }
+ });
+ // Success
+ assertThat(newMap.removeAll(mapping), is(true));
+ // Failure
+ assertThat(newMap.removeAll(mapping), is(false));
+ // Verify operation
+ assertThat(newMap.size(), is(0));
+ assertThat(newMap.isEmpty(), is(true));
+ }
+
+ public static class AsyncConsistentMultimapMock<K, V> extends AsyncConsistentMultimapAdapter<K, V> {
+ private final Multimap<K, V> baseMap;
+ private static final int DEFAULT_CREATION_TIME = 0;
+ private static final int DEFAULT_VERSION = 0;
+
+ AsyncConsistentMultimapMock(Multimap<K, V> newBaseMap) {
+ baseMap = newBaseMap;
+ }
+
+ Versioned<Collection<? extends V>> makeVersioned(Collection<? extends V> v) {
+ return new Versioned<>(v, DEFAULT_VERSION, DEFAULT_CREATION_TIME);
+ }
+
+ @Override
+ public CompletableFuture<Integer> size() {
+ return CompletableFuture.completedFuture(baseMap.size());
+ }
+
+ @Override
+ public CompletableFuture<Boolean> isEmpty() {
+ return CompletableFuture.completedFuture(baseMap.isEmpty());
+ }
+
+ @Override
+ public CompletableFuture<Boolean> putAll(Map<K, Collection<? extends V>> mapping) {
+ CompletableFuture<Boolean> result = CompletableFuture.completedFuture(false);
+ for (Map.Entry<K, Collection<? extends V>> entry : mapping.entrySet()) {
+ if (baseMap.putAll(entry.getKey(), entry.getValue())) {
+ result = CompletableFuture.completedFuture(true);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public CompletableFuture<Versioned<Collection<? extends V>>> get(K key) {
+ return CompletableFuture.completedFuture(makeVersioned(baseMap.get(key)));
+ }
+
+ @Override
+ public CompletableFuture<Boolean> removeAll(Map<K, Collection<? extends V>> mapping) {
+ CompletableFuture<Boolean> result = CompletableFuture.completedFuture(false);
+ for (Map.Entry<K, Collection<? extends V>> entry : mapping.entrySet()) {
+ for (V value : entry.getValue()) {
+ if (baseMap.remove(entry.getKey(), value)) {
+ result = CompletableFuture.completedFuture(true);
+ }
+ }
+ }
+ return result;
+ }
+ }
+}
diff --git a/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMultimapAdapter.java b/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMultimapAdapter.java
new file mode 100644
index 0000000..4bb2618
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMultimapAdapter.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.store.service;
+
+import com.google.common.collect.Multiset;
+import org.onosproject.core.ApplicationId;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+public class AsyncConsistentMultimapAdapter<K, V> implements AsyncConsistentMultimap<K, V> {
+
+ @Override
+ public String name() {
+ return null;
+ }
+
+ @Override
+ public Type primitiveType() {
+ return null;
+ }
+
+ @Override
+ public ApplicationId applicationId() {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Void> destroy() {
+ return null;
+ }
+
+ @Override
+ public void addStatusChangeListener(Consumer<Status> listener) {
+
+ }
+
+ @Override
+ public void removeStatusChangeListener(Consumer<Status> listener) {
+
+ }
+
+ @Override
+ public Collection<Consumer<Status>> statusChangeListeners() {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Integer> size() {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> isEmpty() {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> containsKey(K key) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> containsValue(V value) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> containsEntry(K key, V value) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> put(K key, V value) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Versioned<Collection<? extends V>>> putAndGet(K key, V value) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> remove(K key, V value) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Versioned<Collection<? extends V>>> removeAndGet(K key, V value) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> removeAll(K key, Collection<? extends V> values) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Versioned<Collection<? extends V>>> removeAll(K key) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> removeAll(Map<K, Collection<? extends V>> mapping) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> putAll(K key, Collection<? extends V> values) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> putAll(Map<K, Collection<? extends V>> mapping) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Versioned<Collection<? extends V>>> replaceValues(K key, Collection<V> values) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Void> clear() {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Versioned<Collection<? extends V>>> get(K key) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Set<K>> keySet() {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Multiset<K>> keys() {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Multiset<V>> values() {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Collection<Map.Entry<K, V>>> entries() {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Void> addListener(MultimapEventListener<K, V> listener) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Void> addListener(MultimapEventListener<K, V> listener, Executor executor) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Void> removeListener(MultimapEventListener<K, V> listener) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Map<K, Collection<V>>> asMap() {
+ return null;
+ }
+
+ @Override
+ public ConsistentMultimap<K, V> asMultimap() {
+ return null;
+ }
+
+ @Override
+ public ConsistentMultimap<K, V> asMultimap(long timeoutMillis) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<AsyncIterator<Map.Entry<K, V>>> iterator() {
+ return null;
+ }
+}
diff --git a/core/api/src/test/java/org/onosproject/store/service/TestConsistentMultimap.java b/core/api/src/test/java/org/onosproject/store/service/TestConsistentMultimap.java
index 08be2e4..f70d186 100644
--- a/core/api/src/test/java/org/onosproject/store/service/TestConsistentMultimap.java
+++ b/core/api/src/test/java/org/onosproject/store/service/TestConsistentMultimap.java
@@ -24,6 +24,7 @@
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
/**
* Implementation to test ConsistentMultimap. Very limited.
@@ -103,11 +104,42 @@
}
@Override
+ public boolean removeAll(Map<K, Collection<? extends V>> mapping) {
+ // Semantic is that any change in the map should return true
+ boolean result = false;
+ for (Map.Entry<K, Collection<? extends V>> entry : mapping.entrySet()) {
+ Collection<? extends Versioned<V>> versionedValues = entry.getValue().stream()
+ .map(this::version)
+ .collect(Collectors.toList());
+ for (Versioned<V> value : versionedValues) {
+ if (innermap.remove(entry.getKey(), value)) {
+ result = true;
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
public boolean putAll(K key, Collection<? extends V> values) {
return false;
}
@Override
+ public boolean putAll(Map<K, Collection<? extends V>> mapping) {
+ // Semantic is that any change in the map should return true
+ boolean result = false;
+ for (Map.Entry<K, Collection<? extends V>> entry : mapping.entrySet()) {
+ Collection<? extends Versioned<V>> versionedValues = entry.getValue().stream()
+ .map(this::version).collect(Collectors.toList());
+ if (innermap.putAll(entry.getKey(), versionedValues)) {
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ @Override
public Versioned<Collection<? extends V>> replaceValues(K key, Collection<V> values) {
return null;
}
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/atomix/primitives/impl/AtomixConsistentMultimap.java b/core/store/primitives/src/main/java/org/onosproject/store/atomix/primitives/impl/AtomixConsistentMultimap.java
index 96d7e8c..e444eb5 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/atomix/primitives/impl/AtomixConsistentMultimap.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/atomix/primitives/impl/AtomixConsistentMultimap.java
@@ -109,11 +109,21 @@
}
@Override
+ public CompletableFuture<Boolean> removeAll(Map<K, Collection<? extends V>> mapping) {
+ return adaptMapFuture(atomixMultimap.removeAll(mapping));
+ }
+
+ @Override
public CompletableFuture<Boolean> putAll(K key, Collection<? extends V> values) {
return adaptMapFuture(atomixMultimap.putAll(key, values));
}
@Override
+ public CompletableFuture<Boolean> putAll(Map<K, Collection<? extends V>> mapping) {
+ return adaptMapFuture(atomixMultimap.putAll(mapping));
+ }
+
+ @Override
@SuppressWarnings("unchecked")
public CompletableFuture<Versioned<Collection<? extends V>>> replaceValues(K key, Collection<V> values) {
return adaptMapFuture(atomixMultimap.replaceValues(key, values).thenApply(this::toVersioned));