[ONOS-6342] Refactor transaction architecture to support a shared cache for transactional primitives
Change-Id: I2a17965100895f5aa4d2202028047bb980c11d26
diff --git a/core/api/src/main/java/org/onosproject/store/primitives/MapUpdate.java b/core/api/src/main/java/org/onosproject/store/primitives/MapUpdate.java
index 3b12021..e04f10e 100644
--- a/core/api/src/main/java/org/onosproject/store/primitives/MapUpdate.java
+++ b/core/api/src/main/java/org/onosproject/store/primitives/MapUpdate.java
@@ -19,6 +19,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import java.util.Objects;
import java.util.function.Function;
import org.onlab.util.ByteArraySizeHashPrinter;
@@ -42,6 +43,7 @@
* Insert/Update entry without any checks.
*/
PUT,
+
/**
* Insert an entry iff there is no existing entry for that key.
*/
@@ -73,7 +75,6 @@
REMOVE_IF_VALUE_MATCH,
}
- private String mapName;
private Type type;
private K key;
private V value;
@@ -81,15 +82,6 @@
private long currentVersion = -1;
/**
- * Returns the name of the map.
- *
- * @return map name
- */
- public String mapName() {
- return mapName;
- }
-
- /**
* Returns the type of update operation.
* @return type of update.
*/
@@ -140,7 +132,6 @@
*/
public <S, T> MapUpdate<S, T> map(Function<K, S> keyMapper, Function<V, T> valueMapper) {
return MapUpdate.<S, T>newBuilder()
- .withMapName(mapName)
.withType(type)
.withKey(keyMapper.apply(key))
.withValue(value == null ? null : valueMapper.apply(value))
@@ -150,9 +141,26 @@
}
@Override
+ public int hashCode() {
+ return Objects.hash(type, key, value, currentValue, currentVersion);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof MapUpdate) {
+ MapUpdate that = (MapUpdate) object;
+ return this.type == that.type
+ && Objects.equals(this.key, that.key)
+ && Objects.equals(this.value, that.value)
+ && Objects.equals(this.currentValue, that.currentValue)
+ && Objects.equals(this.currentVersion, that.currentVersion);
+ }
+ return false;
+ }
+
+ @Override
public String toString() {
return MoreObjects.toStringHelper(this)
- .add("mapName", mapName)
.add("type", type)
.add("key", key)
.add("value", value instanceof byte[] ? new ByteArraySizeHashPrinter((byte[]) value) : value)
@@ -187,11 +195,6 @@
return update;
}
- public Builder<K, V> withMapName(String name) {
- update.mapName = checkNotNull(name, "name cannot be null");
- return this;
- }
-
public Builder<K, V> withType(Type type) {
update.type = checkNotNull(type, "type cannot be null");
return this;
diff --git a/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java b/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java
index 8bfd952..0704853 100644
--- a/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java
+++ b/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java
@@ -17,8 +17,8 @@
package org.onosproject.store.service;
import java.util.Collection;
-import java.util.Objects;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -26,10 +26,9 @@
import java.util.function.Function;
import java.util.function.Predicate;
-import org.onosproject.store.primitives.DefaultConsistentMap;
-import org.onosproject.store.primitives.TransactionId;
-
import com.google.common.util.concurrent.MoreExecutors;
+import org.onosproject.store.primitives.DefaultConsistentMap;
+import org.onosproject.store.primitives.MapUpdate;
/**
* A distributed, strongly consistent map whose methods are all executed asynchronously.
@@ -55,7 +54,7 @@
* the returned future will be {@link CompletableFuture#complete completed} when the
* operation finishes.
*/
-public interface AsyncConsistentMap<K, V> extends DistributedPrimitive {
+public interface AsyncConsistentMap<K, V> extends DistributedPrimitive, Transactional<MapUpdate<K, V>> {
@Override
default DistributedPrimitive.Type primitiveType() {
@@ -346,36 +345,6 @@
CompletableFuture<Void> removeListener(MapEventListener<K, V> listener);
/**
- * Prepares a transaction for commitment.
- * @param transaction transaction
- * @return {@code true} if prepare is successful and transaction is ready to be committed;
- * {@code false} otherwise
- */
- CompletableFuture<Boolean> prepare(MapTransaction<K, V> transaction);
-
- /**
- * Commits a previously prepared transaction.
- * @param transactionId transaction identifier
- * @return future that will be completed when the operation finishes
- */
- CompletableFuture<Void> commit(TransactionId transactionId);
-
- /**
- * Aborts a previously prepared transaction.
- * @param transactionId transaction identifier
- * @return future that will be completed when the operation finishes
- */
- CompletableFuture<Void> rollback(TransactionId transactionId);
-
- /**
- * Prepares a transaction and commits it in one go.
- * @param transaction transaction
- * @return {@code true} if operation is successful and updates are committed
- * {@code false} otherwise
- */
- CompletableFuture<Boolean> prepareAndCommit(MapTransaction<K, V> transaction);
-
- /**
* Returns a new {@link ConsistentMap} that is backed by this instance.
*
* @return new {@code ConsistentMap} instance
diff --git a/core/api/src/main/java/org/onosproject/store/service/MapTransaction.java b/core/api/src/main/java/org/onosproject/store/service/MapTransaction.java
deleted file mode 100644
index acedf17..0000000
--- a/core/api/src/main/java/org/onosproject/store/service/MapTransaction.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Laboratory
- *
- * 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 java.util.List;
-import java.util.function.Function;
-
-import org.onosproject.store.primitives.MapUpdate;
-import org.onosproject.store.primitives.TransactionId;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-/**
- * Collection of map updates to be committed atomically.
- *
- * @param <K> key type
- * @param <V> value type
- */
-public class MapTransaction<K, V> {
-
- private final TransactionId transactionId;
- private final List<MapUpdate<K, V>> updates;
-
- public MapTransaction(TransactionId transactionId, List<MapUpdate<K, V>> updates) {
- this.transactionId = transactionId;
- this.updates = ImmutableList.copyOf(updates);
- }
-
- /**
- * Returns the transaction identifier.
- *
- * @return transaction id
- */
- public TransactionId transactionId() {
- return transactionId;
- }
-
- /**
- * Returns the list of map updates.
- *
- * @return map updates
- */
- public List<MapUpdate<K, V>> updates() {
- return updates;
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(getClass())
- .add("transactionId", transactionId)
- .add("updates", updates)
- .toString();
- }
-
- /**
- * Maps this instance to another {@code MapTransaction} with different key and value types.
- *
- * @param keyMapper function for mapping key types
- * @param valueMapper function for mapping value types
- * @return newly typed instance
- *
- * @param <S> key type of returned instance
- * @param <T> value type of returned instance
- */
- public <S, T> MapTransaction<S, T> map(Function<K, S> keyMapper, Function<V, T> valueMapper) {
- return new MapTransaction<>(transactionId, Lists.transform(updates, u -> u.map(keyMapper, valueMapper)));
- }
-}
diff --git a/core/api/src/main/java/org/onosproject/store/service/TransactionLog.java b/core/api/src/main/java/org/onosproject/store/service/TransactionLog.java
new file mode 100644
index 0000000..c367f5d
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/store/service/TransactionLog.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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 java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.onosproject.store.primitives.TransactionId;
+
+/**
+ * Collection of transaction updates to be applied atomically.
+ *
+ * @param <T> log record type
+ */
+public class TransactionLog<T> {
+ private final TransactionId transactionId;
+ private final List<T> records;
+
+ public TransactionLog(TransactionId transactionId, List<T> records) {
+ this.transactionId = transactionId;
+ this.records = ImmutableList.copyOf(records);
+ }
+
+ /**
+ * Returns the transaction identifier.
+ *
+ * @return transaction id
+ */
+ public TransactionId transactionId() {
+ return transactionId;
+ }
+
+ /**
+ * Returns the list of transaction log records.
+ *
+ * @return a list of transaction log records
+ */
+ public List<T> records() {
+ return records;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof TransactionLog) {
+ TransactionLog that = (TransactionLog) object;
+ return this.transactionId.equals(that.transactionId)
+ && this.records.equals(that.records);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(transactionId, records);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("transactionId", transactionId)
+ .add("records", records)
+ .toString();
+ }
+
+ /**
+ * Maps this instance to another {@code MapTransaction} with different key and value types.
+ *
+ * @param mapper function for mapping record types
+ * @return newly typed instance
+ *
+ * @param <U> record type of returned instance
+ */
+ public <U> TransactionLog<U> map(Function<T, U> mapper) {
+ return new TransactionLog<>(transactionId, Lists.transform(records, mapper::apply));
+ }
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/store/service/Transactional.java b/core/api/src/main/java/org/onosproject/store/service/Transactional.java
new file mode 100644
index 0000000..a6e1fbd
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/store/service/Transactional.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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 java.util.concurrent.CompletableFuture;
+
+import org.onosproject.store.primitives.TransactionId;
+
+/**
+ * Interface for transactional primitives.
+ */
+public interface Transactional<T> {
+
+ /**
+ * Begins the transaction.
+ *
+ * @param transactionId the transaction identifier for the transaction to begin
+ * @return a completable future to be completed with the lock version
+ */
+ CompletableFuture<Version> begin(TransactionId transactionId);
+
+ /**
+ * Prepares a transaction for commitment.
+ *
+ * @param transactionLog transaction log
+ * @return {@code true} if prepare is successful and transaction is ready to be committed
+ * {@code false} otherwise
+ */
+ CompletableFuture<Boolean> prepare(TransactionLog<T> transactionLog);
+
+ /**
+ * Prepares and commits a transaction.
+ *
+ * @param transactionLog transaction log
+ * @return {@code true} if prepare is successful and transaction was committed
+ * {@code false} otherwise
+ */
+ CompletableFuture<Boolean> prepareAndCommit(TransactionLog<T> transactionLog);
+
+ /**
+ * Commits a previously prepared transaction and unlocks the object.
+ *
+ * @param transactionId transaction identifier
+ * @return future that will be completed when the operation finishes
+ */
+ CompletableFuture<Void> commit(TransactionId transactionId);
+
+ /**
+ * Aborts a previously prepared transaction and unlocks the object.
+ *
+ * @param transactionId transaction identifier
+ * @return future that will be completed when the operation finishes
+ */
+ CompletableFuture<Void> rollback(TransactionId transactionId);
+
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/store/service/Version.java b/core/api/src/main/java/org/onosproject/store/service/Version.java
new file mode 100644
index 0000000..58a1d3d
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/store/service/Version.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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 java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+import org.onosproject.store.Timestamp;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Logical timestamp for versions.
+ * <p>
+ * The version is a logical timestamp that represents a point in logical time at which an event occurs.
+ * This is used in both pessimistic and optimistic locking protocols to ensure that the state of a shared resource
+ * has not changed at the end of a transaction.
+ */
+public class Version implements Timestamp {
+ private final long version;
+
+ public Version(long version) {
+ this.version = version;
+ }
+
+ @Override
+ public int compareTo(Timestamp o) {
+ checkArgument(o instanceof Version,
+ "Must be LockVersion", o);
+ Version that = (Version) o;
+
+ return ComparisonChain.start()
+ .compare(this.version, that.version)
+ .result();
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(version);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Version)) {
+ return false;
+ }
+ Version that = (Version) obj;
+ return Objects.equals(this.version, that.version);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("version", version)
+ .toString();
+ }
+
+ /**
+ * Returns the lock version.
+ *
+ * @return the lock version
+ */
+ public long value() {
+ return this.version;
+ }
+}
\ No newline at end of file
diff --git a/core/api/src/test/java/org/onosproject/store/primitives/MapUpdateTest.java b/core/api/src/test/java/org/onosproject/store/primitives/MapUpdateTest.java
index 6fecd8c..53cc160 100644
--- a/core/api/src/test/java/org/onosproject/store/primitives/MapUpdateTest.java
+++ b/core/api/src/test/java/org/onosproject/store/primitives/MapUpdateTest.java
@@ -33,7 +33,6 @@
.withValue("2".getBytes())
.withCurrentVersion(3)
.withKey("4")
- .withMapName("5")
.withType(MapUpdate.Type.PUT)
.build();
@@ -42,7 +41,6 @@
.withValue("2".getBytes())
.withCurrentVersion(3)
.withKey("4")
- .withMapName("5")
.withType(MapUpdate.Type.REMOVE)
.build();
@@ -51,7 +49,6 @@
.withValue("2".getBytes())
.withCurrentVersion(3)
.withKey("4")
- .withMapName("5")
.withType(MapUpdate.Type.REMOVE_IF_VALUE_MATCH)
.build();
@@ -60,7 +57,6 @@
.withValue("2".getBytes())
.withCurrentVersion(3)
.withKey("4")
- .withMapName("5")
.withType(MapUpdate.Type.REMOVE_IF_VERSION_MATCH)
.build();
@@ -69,7 +65,6 @@
.withValue("2".getBytes())
.withCurrentVersion(3)
.withKey("4")
- .withMapName("5")
.withType(MapUpdate.Type.PUT_IF_VALUE_MATCH)
.build();
@@ -78,7 +73,6 @@
.withValue("2".getBytes())
.withCurrentVersion(3)
.withKey("4")
- .withMapName("5")
.withType(MapUpdate.Type.PUT_IF_VERSION_MATCH)
.build();
@@ -91,7 +85,6 @@
assertThat(stats1.value(), is("2".getBytes()));
assertThat(stats1.currentVersion(), is(3L));
assertThat(stats1.key(), is("4"));
- assertThat(stats1.mapName(), is("5"));
assertThat(stats1.type(), is(MapUpdate.Type.PUT));
}
diff --git a/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMapAdapter.java b/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMapAdapter.java
index c9161a3..90b4f51 100644
--- a/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMapAdapter.java
+++ b/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMapAdapter.java
@@ -24,6 +24,7 @@
import java.util.function.BiFunction;
import java.util.function.Predicate;
+import org.onosproject.store.primitives.MapUpdate;
import org.onosproject.store.primitives.TransactionId;
/**
@@ -143,7 +144,17 @@
}
@Override
- public CompletableFuture<Boolean> prepare(MapTransaction<K, V> transaction) {
+ public CompletableFuture<Version> begin(TransactionId transactionId) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> prepare(TransactionLog<MapUpdate<K, V>> transactionLog) {
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> prepareAndCommit(TransactionLog<MapUpdate<K, V>> transactionLog) {
return null;
}
@@ -156,10 +167,5 @@
public CompletableFuture<Void> rollback(TransactionId transactionId) {
return null;
}
-
- @Override
- public CompletableFuture<Boolean> prepareAndCommit(MapTransaction<K, V> transaction) {
- return null;
- }
}
diff --git a/core/api/src/test/java/org/onosproject/store/service/VersionTest.java b/core/api/src/test/java/org/onosproject/store/service/VersionTest.java
new file mode 100644
index 0000000..1495ec3
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/store/service/VersionTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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 org.junit.Test;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+/**
+ * Version test.
+ */
+public class VersionTest {
+
+ @Test
+ public void testVersion() {
+ Version version1 = new Version(1);
+ Version version2 = new Version(1);
+ assertTrue(version1.equals(version2));
+ assertTrue(version1.hashCode() == version2.hashCode());
+ assertTrue(version1.value() == version2.value());
+
+ Version version3 = new Version(2);
+ assertFalse(version1.equals(version3));
+ assertFalse(version1.hashCode() == version3.hashCode());
+ assertFalse(version1.value() == version3.value());
+ }
+
+}