Madan Jampani
Committed by Ray Milkey

Added distributed transaction support through a two phase commit protocol

Change-Id: I85d64234a24823fee8b3c2ea830abbb6867dad38
Showing 25 changed files with 951 additions and 376 deletions
1 +/*
2 + * Copyright 2015 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 +package org.onosproject.cli.net;
17 +
18 +import java.util.Collection;
19 +
20 +import org.apache.karaf.shell.commands.Command;
21 +import org.apache.karaf.shell.commands.Option;
22 +import org.onlab.util.Tools;
23 +import org.onosproject.cli.AbstractShellCommand;
24 +import org.onosproject.store.service.StorageAdminService;
25 +import org.onosproject.store.service.Transaction;
26 +
27 +import com.fasterxml.jackson.databind.JsonNode;
28 +import com.fasterxml.jackson.databind.ObjectMapper;
29 +import com.fasterxml.jackson.databind.node.ArrayNode;
30 +import com.fasterxml.jackson.databind.node.ObjectNode;
31 +
32 +/**
33 + * CLI to work with database transactions in the system.
34 + */
35 +@Command(scope = "onos", name = "transactions",
36 + description = "Utility for viewing and redriving database transactions")
37 +public class TransactionsCommand extends AbstractShellCommand {
38 +
39 + @Option(name = "-r", aliases = "--redrive",
40 + description = "Redrive stuck transactions while removing those that are done",
41 + required = false, multiValued = false)
42 + private boolean redrive = false;
43 +
44 + private static final String FMT = "%-20s %-15s %-10s";
45 +
46 + /**
47 + * Displays transactions as text.
48 + *
49 + * @param transactions transactions
50 + */
51 + private void displayTransactions(Collection<Transaction> transactions) {
52 + print("---------------------------------------------");
53 + print(FMT, "Id", "State", "Updated");
54 + print("---------------------------------------------");
55 + transactions.forEach(txn -> print(FMT, txn.id(), txn.state(), Tools.timeAgo(txn.lastUpdated())));
56 + if (transactions.size() > 0) {
57 + print("---------------------------------------------");
58 + }
59 + }
60 +
61 + /**
62 + * Converts collection of transactions into a JSON object.
63 + *
64 + * @param transactions transactions
65 + */
66 + private JsonNode json(Collection<Transaction> transactions) {
67 + ObjectMapper mapper = new ObjectMapper();
68 + ArrayNode txns = mapper.createArrayNode();
69 +
70 + // Create a JSON node for each transaction
71 + transactions.stream().forEach(txn -> {
72 + ObjectNode txnNode = mapper.createObjectNode();
73 + txnNode.put("id", txn.id())
74 + .put("state", txn.state().toString())
75 + .put("lastUpdated", txn.lastUpdated());
76 + txns.add(txnNode);
77 + });
78 +
79 + return txns;
80 + }
81 +
82 + @Override
83 + protected void execute() {
84 + StorageAdminService storageAdminService = get(StorageAdminService.class);
85 +
86 + if (redrive) {
87 + storageAdminService.redriveTransactions();
88 + return;
89 + }
90 +
91 + Collection<Transaction> transactions = storageAdminService.getTransactions();
92 + if (outputJson()) {
93 + print("%s", json(transactions));
94 + } else {
95 + displayTransactions(transactions);
96 + }
97 + }
98 +}
...@@ -233,6 +233,9 @@ ...@@ -233,6 +233,9 @@
233 <action class="org.onosproject.cli.net.MapsListCommand"/> 233 <action class="org.onosproject.cli.net.MapsListCommand"/>
234 </command> 234 </command>
235 <command> 235 <command>
236 + <action class="org.onosproject.cli.net.TransactionsCommand"/>
237 + </command>
238 + <command>
236 <action class="org.onosproject.cli.net.ClusterDevicesCommand"/> 239 <action class="org.onosproject.cli.net.ClusterDevicesCommand"/>
237 <completers> 240 <completers>
238 <ref component-id="clusterIdCompleter"/> 241 <ref component-id="clusterIdCompleter"/>
......
...@@ -35,6 +35,12 @@ public class ConsistentMapException extends RuntimeException { ...@@ -35,6 +35,12 @@ public class ConsistentMapException extends RuntimeException {
35 } 35 }
36 36
37 /** 37 /**
38 + * ConsistentMap update conflicts with an in flight transaction.
39 + */
40 + public static class ConcurrentModification extends ConsistentMapException {
41 + }
42 +
43 + /**
38 * ConsistentMap operation interrupted. 44 * ConsistentMap operation interrupted.
39 */ 45 */
40 public static class Interrupted extends ConsistentMapException { 46 public static class Interrupted extends ConsistentMapException {
......
...@@ -16,36 +16,62 @@ ...@@ -16,36 +16,62 @@
16 16
17 package org.onosproject.store.service; 17 package org.onosproject.store.service;
18 18
19 -import static com.google.common.base.Preconditions.*; 19 +import static com.google.common.base.Preconditions.checkArgument;
20 +import static com.google.common.base.Preconditions.checkNotNull;
21 +import static com.google.common.base.Preconditions.checkState;
20 22
21 import com.google.common.base.MoreObjects; 23 import com.google.common.base.MoreObjects;
22 24
23 /** 25 /**
24 * Database update operation. 26 * Database update operation.
25 * 27 *
26 - * @param <K> key type.
27 - * @param <V> value type.
28 */ 28 */
29 -public class UpdateOperation<K, V> { 29 +public final class DatabaseUpdate {
30 30
31 /** 31 /**
32 * Type of database update operation. 32 * Type of database update operation.
33 */ 33 */
34 public static enum Type { 34 public static enum Type {
35 + /**
36 + * Insert/Update entry without any checks.
37 + */
35 PUT, 38 PUT,
39 + /**
40 + * Insert an entry iff there is no existing entry for that key.
41 + */
36 PUT_IF_ABSENT, 42 PUT_IF_ABSENT,
43 +
44 + /**
45 + * Update entry if the current version matches specified version.
46 + */
37 PUT_IF_VERSION_MATCH, 47 PUT_IF_VERSION_MATCH,
48 +
49 + /**
50 + * Update entry if the current value matches specified value.
51 + */
38 PUT_IF_VALUE_MATCH, 52 PUT_IF_VALUE_MATCH,
53 +
54 + /**
55 + * Remove entry without any checks.
56 + */
39 REMOVE, 57 REMOVE,
58 +
59 + /**
60 + * Remove entry if the current version matches specified version.
61 + */
40 REMOVE_IF_VERSION_MATCH, 62 REMOVE_IF_VERSION_MATCH,
63 +
64 + /**
65 + * Remove entry if the current value matches specified value.
66 + */
41 REMOVE_IF_VALUE_MATCH, 67 REMOVE_IF_VALUE_MATCH,
42 } 68 }
43 69
44 private Type type; 70 private Type type;
45 private String tableName; 71 private String tableName;
46 - private K key; 72 + private String key;
47 - private V value; 73 + private byte[] value;
48 - private V currentValue; 74 + private byte[] currentValue;
49 private long currentVersion = -1; 75 private long currentVersion = -1;
50 76
51 /** 77 /**
...@@ -68,7 +94,7 @@ public class UpdateOperation<K, V> { ...@@ -68,7 +94,7 @@ public class UpdateOperation<K, V> {
68 * Returns the item key being updated. 94 * Returns the item key being updated.
69 * @return item key 95 * @return item key
70 */ 96 */
71 - public K key() { 97 + public String key() {
72 return key; 98 return key;
73 } 99 }
74 100
...@@ -76,7 +102,7 @@ public class UpdateOperation<K, V> { ...@@ -76,7 +102,7 @@ public class UpdateOperation<K, V> {
76 * Returns the new value. 102 * Returns the new value.
77 * @return item's target value. 103 * @return item's target value.
78 */ 104 */
79 - public V value() { 105 + public byte[] value() {
80 return value; 106 return value;
81 } 107 }
82 108
...@@ -84,7 +110,7 @@ public class UpdateOperation<K, V> { ...@@ -84,7 +110,7 @@ public class UpdateOperation<K, V> {
84 * Returns the expected current value in the database value for the key. 110 * Returns the expected current value in the database value for the key.
85 * @return current value in database. 111 * @return current value in database.
86 */ 112 */
87 - public V currentValue() { 113 + public byte[] currentValue() {
88 return currentValue; 114 return currentValue;
89 } 115 }
90 116
...@@ -110,85 +136,81 @@ public class UpdateOperation<K, V> { ...@@ -110,85 +136,81 @@ public class UpdateOperation<K, V> {
110 136
111 /** 137 /**
112 * Creates a new builder instance. 138 * Creates a new builder instance.
113 - * @param <K> key type.
114 - * @param <V> value type.
115 * 139 *
116 * @return builder. 140 * @return builder.
117 */ 141 */
118 - public static <K, V> Builder<K, V> newBuilder() { 142 + public static Builder newBuilder() {
119 - return new Builder<>(); 143 + return new Builder();
120 } 144 }
121 145
122 /** 146 /**
123 - * UpdatOperation builder. 147 + * DatabaseUpdate builder.
124 * 148 *
125 - * @param <K> key type.
126 - * @param <V> value type.
127 */ 149 */
128 - public static final class Builder<K, V> { 150 + public static final class Builder {
129 151
130 - private UpdateOperation<K, V> operation = new UpdateOperation<>(); 152 + private DatabaseUpdate update = new DatabaseUpdate();
131 153
132 - public UpdateOperation<K, V> build() { 154 + public DatabaseUpdate build() {
133 validateInputs(); 155 validateInputs();
134 - return operation; 156 + return update;
135 } 157 }
136 158
137 - public Builder<K, V> withType(Type type) { 159 + public Builder withType(Type type) {
138 - operation.type = checkNotNull(type, "type cannot be null"); 160 + update.type = checkNotNull(type, "type cannot be null");
139 return this; 161 return this;
140 } 162 }
141 163
142 - public Builder<K, V> withTableName(String tableName) { 164 + public Builder withTableName(String tableName) {
143 - operation.tableName = checkNotNull(tableName, "tableName cannot be null"); 165 + update.tableName = checkNotNull(tableName, "tableName cannot be null");
144 return this; 166 return this;
145 } 167 }
146 168
147 - public Builder<K, V> withKey(K key) { 169 + public Builder withKey(String key) {
148 - operation.key = checkNotNull(key, "key cannot be null"); 170 + update.key = checkNotNull(key, "key cannot be null");
149 return this; 171 return this;
150 } 172 }
151 173
152 - public Builder<K, V> withCurrentValue(V value) { 174 + public Builder withCurrentValue(byte[] value) {
153 - operation.currentValue = checkNotNull(value, "currentValue cannot be null"); 175 + update.currentValue = checkNotNull(value, "currentValue cannot be null");
154 return this; 176 return this;
155 } 177 }
156 178
157 - public Builder<K, V> withValue(V value) { 179 + public Builder withValue(byte[] value) {
158 - operation.value = checkNotNull(value, "value cannot be null"); 180 + update.value = checkNotNull(value, "value cannot be null");
159 return this; 181 return this;
160 } 182 }
161 183
162 - public Builder<K, V> withCurrentVersion(long version) { 184 + public Builder withCurrentVersion(long version) {
163 checkArgument(version >= 0, "version cannot be negative"); 185 checkArgument(version >= 0, "version cannot be negative");
164 - operation.currentVersion = version; 186 + update.currentVersion = version;
165 return this; 187 return this;
166 } 188 }
167 189
168 private void validateInputs() { 190 private void validateInputs() {
169 - checkNotNull(operation.type, "type must be specified"); 191 + checkNotNull(update.type, "type must be specified");
170 - checkNotNull(operation.tableName, "table name must be specified"); 192 + checkNotNull(update.tableName, "table name must be specified");
171 - checkNotNull(operation.key, "key must be specified"); 193 + checkNotNull(update.key, "key must be specified");
172 - switch (operation.type) { 194 + switch (update.type) {
173 case PUT: 195 case PUT:
174 case PUT_IF_ABSENT: 196 case PUT_IF_ABSENT:
175 - checkNotNull(operation.value, "value must be specified."); 197 + checkNotNull(update.value, "value must be specified.");
176 break; 198 break;
177 case PUT_IF_VERSION_MATCH: 199 case PUT_IF_VERSION_MATCH:
178 - checkNotNull(operation.value, "value must be specified."); 200 + checkNotNull(update.value, "value must be specified.");
179 - checkState(operation.currentVersion >= 0, "current version must be specified"); 201 + checkState(update.currentVersion >= 0, "current version must be specified");
180 break; 202 break;
181 case PUT_IF_VALUE_MATCH: 203 case PUT_IF_VALUE_MATCH:
182 - checkNotNull(operation.value, "value must be specified."); 204 + checkNotNull(update.value, "value must be specified.");
183 - checkNotNull(operation.currentValue, "currentValue must be specified."); 205 + checkNotNull(update.currentValue, "currentValue must be specified.");
184 break; 206 break;
185 case REMOVE: 207 case REMOVE:
186 break; 208 break;
187 case REMOVE_IF_VERSION_MATCH: 209 case REMOVE_IF_VERSION_MATCH:
188 - checkState(operation.currentVersion >= 0, "current version must be specified"); 210 + checkState(update.currentVersion >= 0, "current version must be specified");
189 break; 211 break;
190 case REMOVE_IF_VALUE_MATCH: 212 case REMOVE_IF_VALUE_MATCH:
191 - checkNotNull(operation.currentValue, "currentValue must be specified."); 213 + checkNotNull(update.currentValue, "currentValue must be specified.");
192 break; 214 break;
193 default: 215 default:
194 throw new IllegalStateException("Unknown operation type"); 216 throw new IllegalStateException("Unknown operation type");
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 */ 15 */
16 package org.onosproject.store.service; 16 package org.onosproject.store.service;
17 17
18 +import java.util.Collection;
18 import java.util.List; 19 import java.util.List;
19 20
20 /** 21 /**
...@@ -35,4 +36,16 @@ public interface StorageAdminService { ...@@ -35,4 +36,16 @@ public interface StorageAdminService {
35 * @return list of map information 36 * @return list of map information
36 */ 37 */
37 List<MapInfo> getMapInfo(); 38 List<MapInfo> getMapInfo();
39 +
40 + /**
41 + * Returns all the transactions in the system.
42 + *
43 + * @return collection of transactions
44 + */
45 + Collection<Transaction> getTransactions();
46 +
47 + /**
48 + * Redrives stuck transactions while removing those that are done.
49 + */
50 + void redriveTransactions();
38 } 51 }
......
...@@ -29,13 +29,6 @@ package org.onosproject.store.service; ...@@ -29,13 +29,6 @@ package org.onosproject.store.service;
29 public interface StorageService { 29 public interface StorageService {
30 30
31 /** 31 /**
32 - * Creates a new transaction context.
33 - *
34 - * @return transaction context
35 - */
36 - TransactionContext createTransactionContext();
37 -
38 - /**
39 * Creates a new EventuallyConsistentMapBuilder. 32 * Creates a new EventuallyConsistentMapBuilder.
40 * 33 *
41 * @param <K> key type 34 * @param <K> key type
...@@ -45,11 +38,11 @@ public interface StorageService { ...@@ -45,11 +38,11 @@ public interface StorageService {
45 <K, V> EventuallyConsistentMapBuilder<K, V> eventuallyConsistentMapBuilder(); 38 <K, V> EventuallyConsistentMapBuilder<K, V> eventuallyConsistentMapBuilder();
46 39
47 /** 40 /**
48 - * Creates a new EventuallyConsistentMapBuilder. 41 + * Creates a new ConsistentMapBuilder.
49 * 42 *
50 * @param <K> key type 43 * @param <K> key type
51 * @param <V> value type 44 * @param <V> value type
52 - * @return builder for an eventually consistent map 45 + * @return builder for a consistent map
53 */ 46 */
54 <K, V> ConsistentMapBuilder<K, V> consistentMapBuilder(); 47 <K, V> ConsistentMapBuilder<K, V> consistentMapBuilder();
55 48
...@@ -60,4 +53,11 @@ public interface StorageService { ...@@ -60,4 +53,11 @@ public interface StorageService {
60 * @return builder for an distributed set 53 * @return builder for an distributed set
61 */ 54 */
62 <E> SetBuilder<E> setBuilder(); 55 <E> SetBuilder<E> setBuilder();
56 +
57 + /**
58 + * Creates a new transaction context.
59 + *
60 + * @return transaction context
61 + */
62 + TransactionContext createTransactionContext();
63 } 63 }
......
1 +/*
2 + * Copyright 2015 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 +package org.onosproject.store.service;
17 +
18 +import java.util.List;
19 +
20 +/**
21 + * An immutable transaction object.
22 + */
23 +public interface Transaction {
24 +
25 + public enum State {
26 + /**
27 + * Indicates a new transaction that is about to be prepared. All transactions
28 + * start their life in this state.
29 + */
30 + PREPARING,
31 +
32 + /**
33 + * Indicates a transaction that is successfully prepared i.e. all participants voted to commit
34 + */
35 + PREPARED,
36 +
37 + /**
38 + * Indicates a transaction that is about to be committed.
39 + */
40 + COMMITTING,
41 +
42 + /**
43 + * Indicates a transaction that has successfully committed.
44 + */
45 + COMMITTED,
46 +
47 + /**
48 + * Indicates a transaction that is about to be rolled back.
49 + */
50 + ROLLINGBACK,
51 +
52 + /**
53 + * Indicates a transaction that has been rolled back and all locks are released.
54 + */
55 + ROLLEDBACK
56 + }
57 +
58 + /**
59 + * Returns the transaction Id.
60 + *
61 + * @return transaction id
62 + */
63 + long id();
64 +
65 + /**
66 + * Returns the list of updates that are part of this transaction.
67 + *
68 + * @return list of database updates
69 + */
70 + List<DatabaseUpdate> updates();
71 +
72 + /**
73 + * Returns the current state of this transaction.
74 + *
75 + * @return transaction state
76 + */
77 + State state();
78 +
79 + /**
80 + * Returns true if this transaction has completed execution.
81 + *
82 + * @return true is yes, false otherwise
83 + */
84 + public default boolean isDone() {
85 + return state() == State.COMMITTED || state() == State.ROLLEDBACK;
86 + }
87 +
88 + /**
89 + * Returns a new transaction that is created by transitioning this one to the specified state.
90 + *
91 + * @param newState destination state
92 + * @return a new transaction instance similar to the current one but its state set to specified state
93 + */
94 + Transaction transition(State newState);
95 +
96 + /**
97 + * Returns the system time when the transaction was last updated.
98 + *
99 + * @return last update time
100 + */
101 + public long lastUpdated();
102 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -19,21 +19,31 @@ package org.onosproject.store.service; ...@@ -19,21 +19,31 @@ package org.onosproject.store.service;
19 /** 19 /**
20 * Provides a context for transactional operations. 20 * Provides a context for transactional operations.
21 * <p> 21 * <p>
22 - * A transaction context provides a boundary within which transactions
23 - * are run. It also is a place where all modifications made within a transaction
24 - * are cached until the point when the transaction commits or aborts. It thus ensures
25 - * isolation of work happening with in the transaction boundary.
26 - * <p>
27 * A transaction context is a vehicle for grouping operations into a unit with the 22 * A transaction context is a vehicle for grouping operations into a unit with the
28 * properties of atomicity, isolation, and durability. Transactions also provide the 23 * properties of atomicity, isolation, and durability. Transactions also provide the
29 * ability to maintain an application's invariants or integrity constraints, 24 * ability to maintain an application's invariants or integrity constraints,
30 * supporting the property of consistency. Together these properties are known as ACID. 25 * supporting the property of consistency. Together these properties are known as ACID.
26 + * <p>
27 + * A transaction context provides a boundary within which transactions
28 + * are run. It also is a place where all modifications made within a transaction
29 + * are cached until the point when the transaction commits or aborts. It thus ensures
30 + * isolation of work happening with in the transaction boundary. Within a transaction
31 + * context isolation level is REPEATABLE_READS i.e. only data that is committed can be read.
32 + * The only uncommitted data that can be read is the data modified by the current transaction.
31 */ 33 */
32 public interface TransactionContext { 34 public interface TransactionContext {
33 35
34 /** 36 /**
37 + * Returns the unique transactionId.
38 + *
39 + * @return transaction id
40 + */
41 + long transactionId();
42 +
43 + /**
35 * Returns if this transaction context is open. 44 * Returns if this transaction context is open.
36 - * @return true if open, false otherwise. 45 + *
46 + * @return true if open, false otherwise
37 */ 47 */
38 boolean isOpen(); 48 boolean isOpen();
39 49
...@@ -45,22 +55,24 @@ public interface TransactionContext { ...@@ -45,22 +55,24 @@ public interface TransactionContext {
45 /** 55 /**
46 * Commits a transaction that was previously started thereby making its changes permanent 56 * Commits a transaction that was previously started thereby making its changes permanent
47 * and externally visible. 57 * and externally visible.
48 - * @throws TransactionException if transaction fails to commit. 58 + *
59 + * @throws TransactionException if transaction fails to commit
49 */ 60 */
50 void commit(); 61 void commit();
51 62
52 /** 63 /**
53 - * Rolls back the current transaction, discarding all its changes. 64 + * Aborts any changes made in this transaction context and discarding all locally cached updates.
54 */ 65 */
55 - void rollback(); 66 + void abort();
56 67
57 /** 68 /**
58 - * Creates a new transactional map. 69 + * Returns a transactional map data structure with the specified name.
70 + *
59 * @param <K> key type 71 * @param <K> key type
60 * @param <V> value type 72 * @param <V> value type
61 - * @param mapName name of the transactional map. 73 + * @param mapName name of the transactional map
62 - * @param serializer serializer to use for encoding/decoding keys and vaulues. 74 + * @param serializer serializer to use for encoding/decoding keys and values of the map
63 - * @return new Transactional Map. 75 + * @return Transactional Map
64 */ 76 */
65 - <K, V> TransactionalMap<K, V> createTransactionalMap(String mapName, Serializer serializer); 77 + <K, V> TransactionalMap<K, V> getTransactionalMap(String mapName, Serializer serializer);
66 } 78 }
......
...@@ -41,8 +41,14 @@ public class TransactionException extends RuntimeException { ...@@ -41,8 +41,14 @@ public class TransactionException extends RuntimeException {
41 } 41 }
42 42
43 /** 43 /**
44 - * Transaction failure due to optimistic concurrency failure. 44 + * Transaction failure due to optimistic concurrency violation.
45 */ 45 */
46 public static class OptimisticConcurrencyFailure extends TransactionException { 46 public static class OptimisticConcurrencyFailure extends TransactionException {
47 } 47 }
48 +
49 + /**
50 + * Transaction failure due to a conflicting transaction in progress.
51 + */
52 + public static class ConcurrentModification extends TransactionException {
53 + }
48 } 54 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -16,15 +16,12 @@ ...@@ -16,15 +16,12 @@
16 16
17 package org.onosproject.store.service; 17 package org.onosproject.store.service;
18 18
19 -import java.util.Collection;
20 -import java.util.Set;
21 -import java.util.Map.Entry;
22 19
23 /** 20 /**
24 * Transactional Map data structure. 21 * Transactional Map data structure.
25 * <p> 22 * <p>
26 - * A TransactionalMap is created by invoking {@link TransactionContext#createTransactionalMap createTransactionalMap} 23 + * A TransactionalMap is created by invoking {@link TransactionContext#getTransactionalMap getTransactionalMap}
27 - * method. All operations performed on this map with in a transaction boundary are invisible externally 24 + * method. All operations performed on this map within a transaction boundary are invisible externally
28 * until the point when the transaction commits. A commit usually succeeds in the absence of conflicts. 25 * until the point when the transaction commits. A commit usually succeeds in the absence of conflicts.
29 * 26 *
30 * @param <K> type of key. 27 * @param <K> type of key.
...@@ -33,36 +30,6 @@ import java.util.Map.Entry; ...@@ -33,36 +30,6 @@ import java.util.Map.Entry;
33 public interface TransactionalMap<K, V> { 30 public interface TransactionalMap<K, V> {
34 31
35 /** 32 /**
36 - * Returns the number of entries in the map.
37 - *
38 - * @return map size.
39 - */
40 - int size();
41 -
42 - /**
43 - * Returns true if the map is empty.
44 - *
45 - * @return true if map has no entries, false otherwise.
46 - */
47 - boolean isEmpty();
48 -
49 - /**
50 - * Returns true if this map contains a mapping for the specified key.
51 - *
52 - * @param key key
53 - * @return true if map contains key, false otherwise.
54 - */
55 - boolean containsKey(K key);
56 -
57 - /**
58 - * Returns true if this map contains the specified value.
59 - *
60 - * @param value value
61 - * @return true if map contains value, false otherwise.
62 - */
63 - boolean containsValue(V value);
64 -
65 - /**
66 * Returns the value to which the specified key is mapped, or null if this 33 * Returns the value to which the specified key is mapped, or null if this
67 * map contains no mapping for the key. 34 * map contains no mapping for the key.
68 * 35 *
...@@ -94,45 +61,6 @@ public interface TransactionalMap<K, V> { ...@@ -94,45 +61,6 @@ public interface TransactionalMap<K, V> {
94 V remove(K key); 61 V remove(K key);
95 62
96 /** 63 /**
97 - * Removes all of the mappings from this map (optional operation).
98 - * The map will be empty after this call returns.
99 - */
100 - void clear();
101 -
102 - /**
103 - * Returns a Set view of the keys contained in this map.
104 - * This method differs from the behavior of java.util.Map.keySet() in that
105 - * what is returned is a unmodifiable snapshot view of the keys in the ConsistentMap.
106 - * Attempts to modify the returned set, whether direct or via its iterator,
107 - * result in an UnsupportedOperationException.
108 - *
109 - * @return a set of the keys contained in this map
110 - */
111 - Set<K> keySet();
112 -
113 - /**
114 - * Returns the collection of values contained in this map.
115 - * This method differs from the behavior of java.util.Map.values() in that
116 - * what is returned is a unmodifiable snapshot view of the values in the ConsistentMap.
117 - * Attempts to modify the returned collection, whether direct or via its iterator,
118 - * result in an UnsupportedOperationException.
119 - *
120 - * @return a collection of the values contained in this map
121 - */
122 - Collection<V> values();
123 -
124 - /**
125 - * Returns the set of entries contained in this map.
126 - * This method differs from the behavior of java.util.Map.entrySet() in that
127 - * what is returned is a unmodifiable snapshot view of the entries in the ConsistentMap.
128 - * Attempts to modify the returned set, whether direct or via its iterator,
129 - * result in an UnsupportedOperationException.
130 - *
131 - * @return set of entries contained in this map.
132 - */
133 - Set<Entry<K, V>> entrySet();
134 -
135 - /**
136 * If the specified key is not already associated with a value 64 * If the specified key is not already associated with a value
137 * associates it with the given value and returns null, else returns the current value. 65 * associates it with the given value and returns null, else returns the current value.
138 * 66 *
......
...@@ -34,6 +34,7 @@ import net.kuujo.copycat.protocol.Consistency; ...@@ -34,6 +34,7 @@ import net.kuujo.copycat.protocol.Consistency;
34 import net.kuujo.copycat.protocol.Protocol; 34 import net.kuujo.copycat.protocol.Protocol;
35 import net.kuujo.copycat.util.concurrent.NamedThreadFactory; 35 import net.kuujo.copycat.util.concurrent.NamedThreadFactory;
36 36
37 +import org.apache.commons.lang.math.RandomUtils;
37 import org.apache.felix.scr.annotations.Activate; 38 import org.apache.felix.scr.annotations.Activate;
38 import org.apache.felix.scr.annotations.Component; 39 import org.apache.felix.scr.annotations.Component;
39 import org.apache.felix.scr.annotations.Deactivate; 40 import org.apache.felix.scr.annotations.Deactivate;
...@@ -41,6 +42,7 @@ import org.apache.felix.scr.annotations.Reference; ...@@ -41,6 +42,7 @@ import org.apache.felix.scr.annotations.Reference;
41 import org.apache.felix.scr.annotations.ReferenceCardinality; 42 import org.apache.felix.scr.annotations.ReferenceCardinality;
42 import org.apache.felix.scr.annotations.Service; 43 import org.apache.felix.scr.annotations.Service;
43 import org.onosproject.cluster.ClusterService; 44 import org.onosproject.cluster.ClusterService;
45 +import org.onosproject.core.IdGenerator;
44 import org.onosproject.store.cluster.impl.DistributedClusterStore; 46 import org.onosproject.store.cluster.impl.DistributedClusterStore;
45 import org.onosproject.store.cluster.impl.NodeInfo; 47 import org.onosproject.store.cluster.impl.NodeInfo;
46 import org.onosproject.store.cluster.messaging.ClusterCommunicationService; 48 import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
...@@ -53,11 +55,13 @@ import org.onosproject.store.service.PartitionInfo; ...@@ -53,11 +55,13 @@ import org.onosproject.store.service.PartitionInfo;
53 import org.onosproject.store.service.SetBuilder; 55 import org.onosproject.store.service.SetBuilder;
54 import org.onosproject.store.service.StorageAdminService; 56 import org.onosproject.store.service.StorageAdminService;
55 import org.onosproject.store.service.StorageService; 57 import org.onosproject.store.service.StorageService;
58 +import org.onosproject.store.service.Transaction;
56 import org.onosproject.store.service.TransactionContext; 59 import org.onosproject.store.service.TransactionContext;
57 import org.slf4j.Logger; 60 import org.slf4j.Logger;
58 61
59 import java.io.File; 62 import java.io.File;
60 import java.io.IOException; 63 import java.io.IOException;
64 +import java.util.Collection;
61 import java.util.List; 65 import java.util.List;
62 import java.util.Map; 66 import java.util.Map;
63 import java.util.Set; 67 import java.util.Set;
...@@ -92,6 +96,9 @@ public class DatabaseManager implements StorageService, StorageAdminService { ...@@ -92,6 +96,9 @@ public class DatabaseManager implements StorageService, StorageAdminService {
92 private PartitionedDatabase partitionedDatabase; 96 private PartitionedDatabase partitionedDatabase;
93 private Database inMemoryDatabase; 97 private Database inMemoryDatabase;
94 98
99 + private TransactionManager transactionManager;
100 + private final IdGenerator transactionIdGenerator = () -> RandomUtils.nextLong();
101 +
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected ClusterService clusterService; 103 protected ClusterService clusterService;
97 104
...@@ -188,6 +195,7 @@ public class DatabaseManager implements StorageService, StorageAdminService { ...@@ -188,6 +195,7 @@ public class DatabaseManager implements StorageService, StorageAdminService {
188 Thread.currentThread().interrupt(); 195 Thread.currentThread().interrupt();
189 log.warn("Failed to complete database initialization."); 196 log.warn("Failed to complete database initialization.");
190 } 197 }
198 + transactionManager = new TransactionManager(partitionedDatabase);
191 log.info("Started"); 199 log.info("Started");
192 } 200 }
193 201
...@@ -218,7 +226,7 @@ public class DatabaseManager implements StorageService, StorageAdminService { ...@@ -218,7 +226,7 @@ public class DatabaseManager implements StorageService, StorageAdminService {
218 226
219 @Override 227 @Override
220 public TransactionContext createTransactionContext() { 228 public TransactionContext createTransactionContext() {
221 - return new DefaultTransactionContext(partitionedDatabase); 229 + return new DefaultTransactionContext(partitionedDatabase, transactionIdGenerator.getNewId());
222 } 230 }
223 231
224 @Override 232 @Override
...@@ -331,6 +339,11 @@ public class DatabaseManager implements StorageService, StorageAdminService { ...@@ -331,6 +339,11 @@ public class DatabaseManager implements StorageService, StorageAdminService {
331 .collect(Collectors.toList()); 339 .collect(Collectors.toList());
332 } 340 }
333 341
342 + @Override
343 + public Collection<Transaction> getTransactions() {
344 + return complete(transactionManager.getTransactions());
345 + }
346 +
334 private static <T> T complete(CompletableFuture<T> future) { 347 private static <T> T complete(CompletableFuture<T> future) {
335 try { 348 try {
336 return future.get(DATABASE_OPERATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 349 return future.get(DATABASE_OPERATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
...@@ -343,4 +356,9 @@ public class DatabaseManager implements StorageService, StorageAdminService { ...@@ -343,4 +356,9 @@ public class DatabaseManager implements StorageService, StorageAdminService {
343 throw new ConsistentMapException(e.getCause()); 356 throw new ConsistentMapException(e.getCause());
344 } 357 }
345 } 358 }
359 +
360 + @Override
361 + public void redriveTransactions() {
362 + getTransactions().stream().forEach(transactionManager::execute);
363 + }
346 } 364 }
......
...@@ -17,12 +17,11 @@ ...@@ -17,12 +17,11 @@
17 package org.onosproject.store.consistent.impl; 17 package org.onosproject.store.consistent.impl;
18 18
19 import java.util.Collection; 19 import java.util.Collection;
20 -import java.util.List;
21 import java.util.Map; 20 import java.util.Map;
22 import java.util.Set; 21 import java.util.Set;
23 import java.util.concurrent.CompletableFuture; 22 import java.util.concurrent.CompletableFuture;
24 23
25 -import org.onosproject.store.service.UpdateOperation; 24 +import org.onosproject.store.service.Transaction;
26 import org.onosproject.store.service.Versioned; 25 import org.onosproject.store.service.Versioned;
27 26
28 /** 27 /**
...@@ -87,7 +86,7 @@ public interface DatabaseProxy<K, V> { ...@@ -87,7 +86,7 @@ public interface DatabaseProxy<K, V> {
87 * @param value The value to set. 86 * @param value The value to set.
88 * @return A completable future to be completed with the result once complete. 87 * @return A completable future to be completed with the result once complete.
89 */ 88 */
90 - CompletableFuture<Versioned<V>> put(String tableName, K key, V value); 89 + CompletableFuture<Result<Versioned<V>>> put(String tableName, K key, V value);
91 90
92 /** 91 /**
93 * Removes a value from the table. 92 * Removes a value from the table.
...@@ -96,7 +95,7 @@ public interface DatabaseProxy<K, V> { ...@@ -96,7 +95,7 @@ public interface DatabaseProxy<K, V> {
96 * @param key The key to remove. 95 * @param key The key to remove.
97 * @return A completable future to be completed with the result once complete. 96 * @return A completable future to be completed with the result once complete.
98 */ 97 */
99 - CompletableFuture<Versioned<V>> remove(String tableName, K key); 98 + CompletableFuture<Result<Versioned<V>>> remove(String tableName, K key);
100 99
101 /** 100 /**
102 * Clears the table. 101 * Clears the table.
...@@ -104,7 +103,7 @@ public interface DatabaseProxy<K, V> { ...@@ -104,7 +103,7 @@ public interface DatabaseProxy<K, V> {
104 * @param tableName table name 103 * @param tableName table name
105 * @return A completable future to be completed with the result once complete. 104 * @return A completable future to be completed with the result once complete.
106 */ 105 */
107 - CompletableFuture<Void> clear(String tableName); 106 + CompletableFuture<Result<Void>> clear(String tableName);
108 107
109 /** 108 /**
110 * Gets a set of keys in the table. 109 * Gets a set of keys in the table.
...@@ -138,7 +137,7 @@ public interface DatabaseProxy<K, V> { ...@@ -138,7 +137,7 @@ public interface DatabaseProxy<K, V> {
138 * @param value The value to set if the given key does not exist. 137 * @param value The value to set if the given key does not exist.
139 * @return A completable future to be completed with the result once complete. 138 * @return A completable future to be completed with the result once complete.
140 */ 139 */
141 - CompletableFuture<Versioned<V>> putIfAbsent(String tableName, K key, V value); 140 + CompletableFuture<Result<Versioned<V>>> putIfAbsent(String tableName, K key, V value);
142 141
143 /** 142 /**
144 * Removes a key and if the existing value for that key matches the specified value. 143 * Removes a key and if the existing value for that key matches the specified value.
...@@ -148,7 +147,7 @@ public interface DatabaseProxy<K, V> { ...@@ -148,7 +147,7 @@ public interface DatabaseProxy<K, V> {
148 * @param value The value to remove. 147 * @param value The value to remove.
149 * @return A completable future to be completed with the result once complete. 148 * @return A completable future to be completed with the result once complete.
150 */ 149 */
151 - CompletableFuture<Boolean> remove(String tableName, K key, V value); 150 + CompletableFuture<Result<Boolean>> remove(String tableName, K key, V value);
152 151
153 /** 152 /**
154 * Removes a key and if the existing version for that key matches the specified version. 153 * Removes a key and if the existing version for that key matches the specified version.
...@@ -158,7 +157,7 @@ public interface DatabaseProxy<K, V> { ...@@ -158,7 +157,7 @@ public interface DatabaseProxy<K, V> {
158 * @param version The expected version. 157 * @param version The expected version.
159 * @return A completable future to be completed with the result once complete. 158 * @return A completable future to be completed with the result once complete.
160 */ 159 */
161 - CompletableFuture<Boolean> remove(String tableName, K key, long version); 160 + CompletableFuture<Result<Boolean>> remove(String tableName, K key, long version);
162 161
163 /** 162 /**
164 * Replaces the entry for the specified key only if currently mapped to the specified value. 163 * Replaces the entry for the specified key only if currently mapped to the specified value.
...@@ -169,7 +168,7 @@ public interface DatabaseProxy<K, V> { ...@@ -169,7 +168,7 @@ public interface DatabaseProxy<K, V> {
169 * @param newValue The value with which to replace the given key and value. 168 * @param newValue The value with which to replace the given key and value.
170 * @return A completable future to be completed with the result once complete. 169 * @return A completable future to be completed with the result once complete.
171 */ 170 */
172 - CompletableFuture<Boolean> replace(String tableName, K key, V oldValue, V newValue); 171 + CompletableFuture<Result<Boolean>> replace(String tableName, K key, V oldValue, V newValue);
173 172
174 /** 173 /**
175 * Replaces the entry for the specified key only if currently mapped to the specified version. 174 * Replaces the entry for the specified key only if currently mapped to the specified version.
...@@ -180,14 +179,42 @@ public interface DatabaseProxy<K, V> { ...@@ -180,14 +179,42 @@ public interface DatabaseProxy<K, V> {
180 * @param newValue The value with which to replace the given key and version. 179 * @param newValue The value with which to replace the given key and version.
181 * @return A completable future to be completed with the result once complete. 180 * @return A completable future to be completed with the result once complete.
182 */ 181 */
183 - CompletableFuture<Boolean> replace(String tableName, K key, long oldVersion, V newValue); 182 + CompletableFuture<Result<Boolean>> replace(String tableName, K key, long oldVersion, V newValue);
184 183
185 /** 184 /**
186 - * Perform a atomic batch update operation i.e. either all operations in batch succeed or 185 + * Prepare and commit the specified transaction.
187 - * none do and no state changes are made.
188 * 186 *
189 - * @param updates list of updates to apply atomically. 187 + * @param transaction transaction to commit (after preparation)
190 - * @return A completable future to be completed with the result once complete. 188 + * @return A completable future to be completed with the result once complete
189 + */
190 + CompletableFuture<Boolean> prepareAndCommit(Transaction transaction);
191 +
192 + /**
193 + * Prepare the specified transaction for commit. A successful prepare implies
194 + * all the affected resources are locked thus ensuring no concurrent updates can interfere.
195 + *
196 + * @param transaction transaction to prepare (for commit)
197 + * @return A completable future to be completed with the result once complete. The future is completed
198 + * with true if the transaction is successfully prepared i.e. all pre-conditions are met and
199 + * applicable resources locked.
200 + */
201 + CompletableFuture<Boolean> prepare(Transaction transaction);
202 +
203 + /**
204 + * Commit the specified transaction. A successful commit implies
205 + * all the updates are applied, are now durable and are now visible externally.
206 + *
207 + * @param transaction transaction to commit
208 + * @return A completable future to be completed with the result once complete
209 + */
210 + CompletableFuture<Boolean> commit(Transaction transaction);
211 +
212 + /**
213 + * Rollback the specified transaction. A successful rollback implies
214 + * all previously acquired locks for the affected resources are released.
215 + *
216 + * @param transaction transaction to rollback
217 + * @return A completable future to be completed with the result once complete
191 */ 218 */
192 - CompletableFuture<Boolean> atomicBatchUpdate(List<UpdateOperation<K, V>> updates); 219 + CompletableFuture<Boolean> rollback(Transaction transaction);
193 } 220 }
......
...@@ -23,6 +23,8 @@ import org.apache.commons.lang3.tuple.Pair; ...@@ -23,6 +23,8 @@ import org.apache.commons.lang3.tuple.Pair;
23 import org.onlab.util.KryoNamespace; 23 import org.onlab.util.KryoNamespace;
24 import org.onosproject.store.serializers.KryoNamespaces; 24 import org.onosproject.store.serializers.KryoNamespaces;
25 import org.onosproject.store.serializers.KryoSerializer; 25 import org.onosproject.store.serializers.KryoSerializer;
26 +import org.onosproject.store.service.DatabaseUpdate;
27 +import org.onosproject.store.service.Transaction;
26 import org.onosproject.store.service.Versioned; 28 import org.onosproject.store.service.Versioned;
27 29
28 import net.kuujo.copycat.cluster.internal.MemberInfo; 30 import net.kuujo.copycat.cluster.internal.MemberInfo;
...@@ -63,8 +65,14 @@ public class DatabaseSerializer extends SerializerConfig { ...@@ -63,8 +65,14 @@ public class DatabaseSerializer extends SerializerConfig {
63 private static final KryoNamespace ONOS_STORE = KryoNamespace.newBuilder() 65 private static final KryoNamespace ONOS_STORE = KryoNamespace.newBuilder()
64 .nextId(KryoNamespace.FLOATING_ID) 66 .nextId(KryoNamespace.FLOATING_ID)
65 .register(Versioned.class) 67 .register(Versioned.class)
68 + .register(DatabaseUpdate.class)
69 + .register(DatabaseUpdate.Type.class)
66 .register(Pair.class) 70 .register(Pair.class)
67 .register(ImmutablePair.class) 71 .register(ImmutablePair.class)
72 + .register(Result.class)
73 + .register(Result.Status.class)
74 + .register(DefaultTransaction.class)
75 + .register(Transaction.State.class)
68 .build(); 76 .build();
69 77
70 private static final KryoSerializer SERIALIZER = new KryoSerializer() { 78 private static final KryoSerializer SERIALIZER = new KryoSerializer() {
......
...@@ -17,11 +17,10 @@ ...@@ -17,11 +17,10 @@
17 package org.onosproject.store.consistent.impl; 17 package org.onosproject.store.consistent.impl;
18 18
19 import java.util.Collection; 19 import java.util.Collection;
20 -import java.util.List;
21 import java.util.Map.Entry; 20 import java.util.Map.Entry;
22 import java.util.Set; 21 import java.util.Set;
23 22
24 -import org.onosproject.store.service.UpdateOperation; 23 +import org.onosproject.store.service.Transaction;
25 import org.onosproject.store.service.Versioned; 24 import org.onosproject.store.service.Versioned;
26 25
27 import net.kuujo.copycat.state.Command; 26 import net.kuujo.copycat.state.Command;
...@@ -62,13 +61,13 @@ public interface DatabaseState<K, V> { ...@@ -62,13 +61,13 @@ public interface DatabaseState<K, V> {
62 Versioned<V> get(String tableName, K key); 61 Versioned<V> get(String tableName, K key);
63 62
64 @Command 63 @Command
65 - Versioned<V> put(String tableName, K key, V value); 64 + Result<Versioned<V>> put(String tableName, K key, V value);
66 65
67 @Command 66 @Command
68 - Versioned<V> remove(String tableName, K key); 67 + Result<Versioned<V>> remove(String tableName, K key);
69 68
70 @Command 69 @Command
71 - void clear(String tableName); 70 + Result<Void> clear(String tableName);
72 71
73 @Query 72 @Query
74 Set<K> keySet(String tableName); 73 Set<K> keySet(String tableName);
...@@ -80,20 +79,29 @@ public interface DatabaseState<K, V> { ...@@ -80,20 +79,29 @@ public interface DatabaseState<K, V> {
80 Set<Entry<K, Versioned<V>>> entrySet(String tableName); 79 Set<Entry<K, Versioned<V>>> entrySet(String tableName);
81 80
82 @Command 81 @Command
83 - Versioned<V> putIfAbsent(String tableName, K key, V value); 82 + Result<Versioned<V>> putIfAbsent(String tableName, K key, V value);
84 83
85 @Command 84 @Command
86 - boolean remove(String tableName, K key, V value); 85 + Result<Boolean> remove(String tableName, K key, V value);
87 86
88 @Command 87 @Command
89 - boolean remove(String tableName, K key, long version); 88 + Result<Boolean> remove(String tableName, K key, long version);
90 89
91 @Command 90 @Command
92 - boolean replace(String tableName, K key, V oldValue, V newValue); 91 + Result<Boolean> replace(String tableName, K key, V oldValue, V newValue);
93 92
94 @Command 93 @Command
95 - boolean replace(String tableName, K key, long oldVersion, V newValue); 94 + Result<Boolean> replace(String tableName, K key, long oldVersion, V newValue);
96 95
97 @Command 96 @Command
98 - boolean batchUpdate(List<UpdateOperation<K, V>> updates); 97 + boolean prepareAndCommit(Transaction transaction);
98 +
99 + @Command
100 + boolean prepare(Transaction transaction);
101 +
102 + @Command
103 + boolean commit(Transaction transaction);
104 +
105 + @Command
106 + boolean rollback(Transaction transaction);
99 } 107 }
......
...@@ -28,6 +28,7 @@ import java.util.Set; ...@@ -28,6 +28,7 @@ import java.util.Set;
28 import org.apache.commons.lang3.tuple.Pair; 28 import org.apache.commons.lang3.tuple.Pair;
29 import org.onlab.util.HexString; 29 import org.onlab.util.HexString;
30 import org.onosproject.store.service.AsyncConsistentMap; 30 import org.onosproject.store.service.AsyncConsistentMap;
31 +import org.onosproject.store.service.ConsistentMapException;
31 import org.onosproject.store.service.Serializer; 32 import org.onosproject.store.service.Serializer;
32 import org.onosproject.store.service.Versioned; 33 import org.onosproject.store.service.Versioned;
33 34
...@@ -108,6 +109,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V> ...@@ -108,6 +109,7 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V>
108 checkNotNull(key, ERROR_NULL_KEY); 109 checkNotNull(key, ERROR_NULL_KEY);
109 checkNotNull(value, ERROR_NULL_VALUE); 110 checkNotNull(value, ERROR_NULL_VALUE);
110 return database.put(name, keyCache.getUnchecked(key), serializer.encode(value)) 111 return database.put(name, keyCache.getUnchecked(key), serializer.encode(value))
112 + .thenApply(this::unwrapResult)
111 .thenApply(v -> v != null 113 .thenApply(v -> v != null
112 ? new Versioned<>(serializer.decode(v.value()), v.version(), v.creationTime()) : null); 114 ? new Versioned<>(serializer.decode(v.value()), v.version(), v.creationTime()) : null);
113 } 115 }
...@@ -116,13 +118,14 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V> ...@@ -116,13 +118,14 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V>
116 public CompletableFuture<Versioned<V>> remove(K key) { 118 public CompletableFuture<Versioned<V>> remove(K key) {
117 checkNotNull(key, ERROR_NULL_KEY); 119 checkNotNull(key, ERROR_NULL_KEY);
118 return database.remove(name, keyCache.getUnchecked(key)) 120 return database.remove(name, keyCache.getUnchecked(key))
121 + .thenApply(this::unwrapResult)
119 .thenApply(v -> v != null 122 .thenApply(v -> v != null
120 ? new Versioned<>(serializer.decode(v.value()), v.version(), v.creationTime()) : null); 123 ? new Versioned<>(serializer.decode(v.value()), v.version(), v.creationTime()) : null);
121 } 124 }
122 125
123 @Override 126 @Override
124 public CompletableFuture<Void> clear() { 127 public CompletableFuture<Void> clear() {
125 - return database.clear(name); 128 + return database.clear(name).thenApply(this::unwrapResult);
126 } 129 }
127 130
128 @Override 131 @Override
...@@ -154,9 +157,11 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V> ...@@ -154,9 +157,11 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V>
154 public CompletableFuture<Versioned<V>> putIfAbsent(K key, V value) { 157 public CompletableFuture<Versioned<V>> putIfAbsent(K key, V value) {
155 checkNotNull(key, ERROR_NULL_KEY); 158 checkNotNull(key, ERROR_NULL_KEY);
156 checkNotNull(value, ERROR_NULL_VALUE); 159 checkNotNull(value, ERROR_NULL_VALUE);
157 - return database.putIfAbsent( 160 + return database.putIfAbsent(name,
158 - name, keyCache.getUnchecked(key), serializer.encode(value)).thenApply(v -> 161 + keyCache.getUnchecked(key),
159 - v != null ? 162 + serializer.encode(value))
163 + .thenApply(this::unwrapResult)
164 + .thenApply(v -> v != null ?
160 new Versioned<>(serializer.decode(v.value()), v.version(), v.creationTime()) : null); 165 new Versioned<>(serializer.decode(v.value()), v.version(), v.creationTime()) : null);
161 } 166 }
162 167
...@@ -164,13 +169,15 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V> ...@@ -164,13 +169,15 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V>
164 public CompletableFuture<Boolean> remove(K key, V value) { 169 public CompletableFuture<Boolean> remove(K key, V value) {
165 checkNotNull(key, ERROR_NULL_KEY); 170 checkNotNull(key, ERROR_NULL_KEY);
166 checkNotNull(value, ERROR_NULL_VALUE); 171 checkNotNull(value, ERROR_NULL_VALUE);
167 - return database.remove(name, keyCache.getUnchecked(key), serializer.encode(value)); 172 + return database.remove(name, keyCache.getUnchecked(key), serializer.encode(value))
173 + .thenApply(this::unwrapResult);
168 } 174 }
169 175
170 @Override 176 @Override
171 public CompletableFuture<Boolean> remove(K key, long version) { 177 public CompletableFuture<Boolean> remove(K key, long version) {
172 checkNotNull(key, ERROR_NULL_KEY); 178 checkNotNull(key, ERROR_NULL_KEY);
173 - return database.remove(name, keyCache.getUnchecked(key), version); 179 + return database.remove(name, keyCache.getUnchecked(key), version)
180 + .thenApply(this::unwrapResult);
174 181
175 } 182 }
176 183
...@@ -179,14 +186,16 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V> ...@@ -179,14 +186,16 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V>
179 checkNotNull(key, ERROR_NULL_KEY); 186 checkNotNull(key, ERROR_NULL_KEY);
180 checkNotNull(newValue, ERROR_NULL_VALUE); 187 checkNotNull(newValue, ERROR_NULL_VALUE);
181 byte[] existing = oldValue != null ? serializer.encode(oldValue) : null; 188 byte[] existing = oldValue != null ? serializer.encode(oldValue) : null;
182 - return database.replace(name, keyCache.getUnchecked(key), existing, serializer.encode(newValue)); 189 + return database.replace(name, keyCache.getUnchecked(key), existing, serializer.encode(newValue))
190 + .thenApply(this::unwrapResult);
183 } 191 }
184 192
185 @Override 193 @Override
186 public CompletableFuture<Boolean> replace(K key, long oldVersion, V newValue) { 194 public CompletableFuture<Boolean> replace(K key, long oldVersion, V newValue) {
187 checkNotNull(key, ERROR_NULL_KEY); 195 checkNotNull(key, ERROR_NULL_KEY);
188 checkNotNull(newValue, ERROR_NULL_VALUE); 196 checkNotNull(newValue, ERROR_NULL_VALUE);
189 - return database.replace(name, keyCache.getUnchecked(key), oldVersion, serializer.encode(newValue)); 197 + return database.replace(name, keyCache.getUnchecked(key), oldVersion, serializer.encode(newValue))
198 + .thenApply(this::unwrapResult);
190 } 199 }
191 200
192 private Map.Entry<K, Versioned<V>> fromRawEntry(Map.Entry<String, Versioned<byte[]>> e) { 201 private Map.Entry<K, Versioned<V>> fromRawEntry(Map.Entry<String, Versioned<byte[]>> e) {
...@@ -197,4 +206,14 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V> ...@@ -197,4 +206,14 @@ public class DefaultAsyncConsistentMap<K, V> implements AsyncConsistentMap<K, V>
197 e.getValue().version(), 206 e.getValue().version(),
198 e.getValue().creationTime())); 207 e.getValue().creationTime()));
199 } 208 }
209 +
210 + private <T> T unwrapResult(Result<T> result) {
211 + if (result.status() == Result.Status.LOCKED) {
212 + throw new ConsistentMapException.ConcurrentModification();
213 + } else if (result.success()) {
214 + return result.value();
215 + } else {
216 + throw new IllegalStateException("Must not be here");
217 + }
218 + }
200 } 219 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -23,13 +23,12 @@ import net.kuujo.copycat.state.internal.DefaultStateMachine; ...@@ -23,13 +23,12 @@ import net.kuujo.copycat.state.internal.DefaultStateMachine;
23 import net.kuujo.copycat.util.concurrent.Futures; 23 import net.kuujo.copycat.util.concurrent.Futures;
24 24
25 import java.util.Collection; 25 import java.util.Collection;
26 -import java.util.List;
27 import java.util.Map; 26 import java.util.Map;
28 import java.util.Set; 27 import java.util.Set;
29 import java.util.concurrent.CompletableFuture; 28 import java.util.concurrent.CompletableFuture;
30 import java.util.function.Supplier; 29 import java.util.function.Supplier;
31 30
32 -import org.onosproject.store.service.UpdateOperation; 31 +import org.onosproject.store.service.Transaction;
33 import org.onosproject.store.service.Versioned; 32 import org.onosproject.store.service.Versioned;
34 33
35 /** 34 /**
...@@ -39,7 +38,7 @@ public class DefaultDatabase extends AbstractResource<Database> implements Datab ...@@ -39,7 +38,7 @@ public class DefaultDatabase extends AbstractResource<Database> implements Datab
39 private final StateMachine<DatabaseState<String, byte[]>> stateMachine; 38 private final StateMachine<DatabaseState<String, byte[]>> stateMachine;
40 private DatabaseProxy<String, byte[]> proxy; 39 private DatabaseProxy<String, byte[]> proxy;
41 40
42 - @SuppressWarnings("unchecked") 41 + @SuppressWarnings({ "unchecked", "rawtypes" })
43 public DefaultDatabase(ResourceContext context) { 42 public DefaultDatabase(ResourceContext context) {
44 super(context); 43 super(context);
45 this.stateMachine = new DefaultStateMachine(context, DatabaseState.class, DefaultDatabaseState.class); 44 this.stateMachine = new DefaultStateMachine(context, DatabaseState.class, DefaultDatabaseState.class);
...@@ -91,17 +90,17 @@ public class DefaultDatabase extends AbstractResource<Database> implements Datab ...@@ -91,17 +90,17 @@ public class DefaultDatabase extends AbstractResource<Database> implements Datab
91 } 90 }
92 91
93 @Override 92 @Override
94 - public CompletableFuture<Versioned<byte[]>> put(String tableName, String key, byte[] value) { 93 + public CompletableFuture<Result<Versioned<byte[]>>> put(String tableName, String key, byte[] value) {
95 return checkOpen(() -> proxy.put(tableName, key, value)); 94 return checkOpen(() -> proxy.put(tableName, key, value));
96 } 95 }
97 96
98 @Override 97 @Override
99 - public CompletableFuture<Versioned<byte[]>> remove(String tableName, String key) { 98 + public CompletableFuture<Result<Versioned<byte[]>>> remove(String tableName, String key) {
100 return checkOpen(() -> proxy.remove(tableName, key)); 99 return checkOpen(() -> proxy.remove(tableName, key));
101 } 100 }
102 101
103 @Override 102 @Override
104 - public CompletableFuture<Void> clear(String tableName) { 103 + public CompletableFuture<Result<Void>> clear(String tableName) {
105 return checkOpen(() -> proxy.clear(tableName)); 104 return checkOpen(() -> proxy.clear(tableName));
106 } 105 }
107 106
...@@ -121,33 +120,48 @@ public class DefaultDatabase extends AbstractResource<Database> implements Datab ...@@ -121,33 +120,48 @@ public class DefaultDatabase extends AbstractResource<Database> implements Datab
121 } 120 }
122 121
123 @Override 122 @Override
124 - public CompletableFuture<Versioned<byte[]>> putIfAbsent(String tableName, String key, byte[] value) { 123 + public CompletableFuture<Result<Versioned<byte[]>>> putIfAbsent(String tableName, String key, byte[] value) {
125 return checkOpen(() -> proxy.putIfAbsent(tableName, key, value)); 124 return checkOpen(() -> proxy.putIfAbsent(tableName, key, value));
126 } 125 }
127 126
128 @Override 127 @Override
129 - public CompletableFuture<Boolean> remove(String tableName, String key, byte[] value) { 128 + public CompletableFuture<Result<Boolean>> remove(String tableName, String key, byte[] value) {
130 return checkOpen(() -> proxy.remove(tableName, key, value)); 129 return checkOpen(() -> proxy.remove(tableName, key, value));
131 } 130 }
132 131
133 @Override 132 @Override
134 - public CompletableFuture<Boolean> remove(String tableName, String key, long version) { 133 + public CompletableFuture<Result<Boolean>> remove(String tableName, String key, long version) {
135 return checkOpen(() -> proxy.remove(tableName, key, version)); 134 return checkOpen(() -> proxy.remove(tableName, key, version));
136 } 135 }
137 136
138 @Override 137 @Override
139 - public CompletableFuture<Boolean> replace(String tableName, String key, byte[] oldValue, byte[] newValue) { 138 + public CompletableFuture<Result<Boolean>> replace(String tableName, String key, byte[] oldValue, byte[] newValue) {
140 return checkOpen(() -> proxy.replace(tableName, key, oldValue, newValue)); 139 return checkOpen(() -> proxy.replace(tableName, key, oldValue, newValue));
141 } 140 }
142 141
143 @Override 142 @Override
144 - public CompletableFuture<Boolean> replace(String tableName, String key, long oldVersion, byte[] newValue) { 143 + public CompletableFuture<Result<Boolean>> replace(String tableName, String key, long oldVersion, byte[] newValue) {
145 return checkOpen(() -> proxy.replace(tableName, key, oldVersion, newValue)); 144 return checkOpen(() -> proxy.replace(tableName, key, oldVersion, newValue));
146 } 145 }
147 146
148 @Override 147 @Override
149 - public CompletableFuture<Boolean> atomicBatchUpdate(List<UpdateOperation<String, byte[]>> updates) { 148 + public CompletableFuture<Boolean> prepareAndCommit(Transaction transaction) {
150 - return checkOpen(() -> proxy.atomicBatchUpdate(updates)); 149 + return checkOpen(() -> proxy.prepareAndCommit(transaction));
150 + }
151 +
152 + @Override
153 + public CompletableFuture<Boolean> prepare(Transaction transaction) {
154 + return checkOpen(() -> proxy.prepare(transaction));
155 + }
156 +
157 + @Override
158 + public CompletableFuture<Boolean> commit(Transaction transaction) {
159 + return checkOpen(() -> proxy.commit(transaction));
160 + }
161 +
162 + @Override
163 + public CompletableFuture<Boolean> rollback(Transaction transaction) {
164 + return checkOpen(() -> proxy.rollback(transaction));
151 } 165 }
152 166
153 @Override 167 @Override
......
...@@ -18,6 +18,7 @@ package org.onosproject.store.consistent.impl; ...@@ -18,6 +18,7 @@ package org.onosproject.store.consistent.impl;
18 import java.util.Collection; 18 import java.util.Collection;
19 import java.util.Iterator; 19 import java.util.Iterator;
20 import java.util.Set; 20 import java.util.Set;
21 +
21 import org.onosproject.store.service.ConsistentMap; 22 import org.onosproject.store.service.ConsistentMap;
22 import org.onosproject.store.service.Serializer; 23 import org.onosproject.store.service.Serializer;
23 24
...@@ -46,6 +47,7 @@ public class DefaultDistributedSet<E> implements Set<E> { ...@@ -46,6 +47,7 @@ public class DefaultDistributedSet<E> implements Set<E> {
46 return backingMap.isEmpty(); 47 return backingMap.isEmpty();
47 } 48 }
48 49
50 + @SuppressWarnings("unchecked")
49 @Override 51 @Override
50 public boolean contains(Object o) { 52 public boolean contains(Object o) {
51 return backingMap.containsKey((E) o); 53 return backingMap.containsKey((E) o);
...@@ -71,6 +73,7 @@ public class DefaultDistributedSet<E> implements Set<E> { ...@@ -71,6 +73,7 @@ public class DefaultDistributedSet<E> implements Set<E> {
71 return backingMap.putIfAbsent(e, true) == null; 73 return backingMap.putIfAbsent(e, true) == null;
72 } 74 }
73 75
76 + @SuppressWarnings("unchecked")
74 @Override 77 @Override
75 public boolean remove(Object o) { 78 public boolean remove(Object o) {
76 return backingMap.remove((E) o, true); 79 return backingMap.remove((E) o, true);
......
1 +/*
2 + * Copyright 2015 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 +package org.onosproject.store.consistent.impl;
17 +
18 +import java.util.List;
19 +
20 +import org.onosproject.store.service.DatabaseUpdate;
21 +import org.onosproject.store.service.Transaction;
22 +
23 +import com.google.common.collect.ImmutableList;
24 +
25 +/**
26 + * A Default transaction implementation.
27 + */
28 +public class DefaultTransaction implements Transaction {
29 +
30 + private final long transactionId;
31 + private final List<DatabaseUpdate> updates;
32 + private final State state;
33 + private final long lastUpdated;
34 +
35 + public DefaultTransaction(long transactionId, List<DatabaseUpdate> updates) {
36 + this(transactionId, updates, State.PREPARING, System.currentTimeMillis());
37 + }
38 +
39 + private DefaultTransaction(long transactionId, List<DatabaseUpdate> updates, State state, long lastUpdated) {
40 + this.transactionId = transactionId;
41 + this.updates = ImmutableList.copyOf(updates);
42 + this.state = state;
43 + this.lastUpdated = lastUpdated;
44 + }
45 +
46 + @Override
47 + public long id() {
48 + return transactionId;
49 + }
50 +
51 + @Override
52 + public List<DatabaseUpdate> updates() {
53 + return updates;
54 + }
55 +
56 + @Override
57 + public State state() {
58 + return state;
59 + }
60 +
61 + @Override
62 + public Transaction transition(State newState) {
63 + return new DefaultTransaction(transactionId, updates, newState, System.currentTimeMillis());
64 + }
65 +
66 + @Override
67 + public long lastUpdated() {
68 + return lastUpdated;
69 + }
70 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -18,19 +18,14 @@ package org.onosproject.store.consistent.impl; ...@@ -18,19 +18,14 @@ package org.onosproject.store.consistent.impl;
18 18
19 import java.util.List; 19 import java.util.List;
20 import java.util.Map; 20 import java.util.Map;
21 -import java.util.concurrent.CompletableFuture;
22 -import java.util.concurrent.ExecutionException;
23 -import java.util.concurrent.TimeUnit;
24 -import java.util.concurrent.TimeoutException;
25 21
26 import static com.google.common.base.Preconditions.*; 22 import static com.google.common.base.Preconditions.*;
27 23
28 -import org.onosproject.store.service.ConsistentMap; 24 +import org.onosproject.store.service.DatabaseUpdate;
29 import org.onosproject.store.service.Serializer; 25 import org.onosproject.store.service.Serializer;
30 import org.onosproject.store.service.TransactionContext; 26 import org.onosproject.store.service.TransactionContext;
31 import org.onosproject.store.service.TransactionException; 27 import org.onosproject.store.service.TransactionException;
32 import org.onosproject.store.service.TransactionalMap; 28 import org.onosproject.store.service.TransactionalMap;
33 -import org.onosproject.store.service.UpdateOperation;
34 29
35 import com.google.common.collect.Lists; 30 import com.google.common.collect.Lists;
36 import com.google.common.collect.Maps; 31 import com.google.common.collect.Maps;
...@@ -40,80 +35,69 @@ import com.google.common.collect.Maps; ...@@ -40,80 +35,69 @@ import com.google.common.collect.Maps;
40 */ 35 */
41 public class DefaultTransactionContext implements TransactionContext { 36 public class DefaultTransactionContext implements TransactionContext {
42 37
43 - private final Map<String, DefaultTransactionalMap> txMaps = Maps.newHashMap(); 38 + private static final String TX_NOT_OPEN_ERROR = "Transaction Context is not open";
39 +
40 + @SuppressWarnings("rawtypes")
41 + private final Map<String, DefaultTransactionalMap> txMaps = Maps.newConcurrentMap();
44 private boolean isOpen = false; 42 private boolean isOpen = false;
45 private final Database database; 43 private final Database database;
46 - private static final String TX_NOT_OPEN_ERROR = "Transaction is not open"; 44 + private final long transactionId;
47 - private static final int TRANSACTION_TIMEOUT_MILLIS = 2000;
48 45
49 - DefaultTransactionContext(Database database) { 46 + public DefaultTransactionContext(Database database, long transactionId) {
50 - this.database = checkNotNull(database, "Database must not be null"); 47 + this.database = checkNotNull(database);
48 + this.transactionId = transactionId;
49 + }
50 +
51 + @Override
52 + public long transactionId() {
53 + return transactionId;
51 } 54 }
52 55
53 @Override 56 @Override
54 public void begin() { 57 public void begin() {
58 + checkState(!isOpen, "Transaction Context is already open");
55 isOpen = true; 59 isOpen = true;
56 } 60 }
57 61
58 @Override 62 @Override
63 + public boolean isOpen() {
64 + return isOpen;
65 + }
66 +
67 + @Override
59 @SuppressWarnings("unchecked") 68 @SuppressWarnings("unchecked")
60 - public <K, V> TransactionalMap<K, V> createTransactionalMap(String mapName, 69 + public <K, V> TransactionalMap<K, V> getTransactionalMap(String mapName,
61 Serializer serializer) { 70 Serializer serializer) {
62 - checkNotNull(mapName, "map name is null");
63 - checkNotNull(serializer, "serializer is null");
64 checkState(isOpen, TX_NOT_OPEN_ERROR); 71 checkState(isOpen, TX_NOT_OPEN_ERROR);
65 - if (!txMaps.containsKey(mapName)) { 72 + checkNotNull(mapName);
66 - ConsistentMap<K, V> backingMap = new DefaultConsistentMap<>(mapName, database, serializer); 73 + checkNotNull(serializer);
67 - DefaultTransactionalMap<K, V> txMap = new DefaultTransactionalMap<>(mapName, backingMap, this, serializer); 74 + return txMaps.computeIfAbsent(mapName, name -> new DefaultTransactionalMap<>(
68 - txMaps.put(mapName, txMap); 75 + name,
69 - } 76 + new DefaultConsistentMap<>(name, database, serializer),
70 - return txMaps.get(mapName); 77 + this,
78 + serializer));
71 } 79 }
72 80
73 @SuppressWarnings("unchecked") 81 @SuppressWarnings("unchecked")
74 @Override 82 @Override
75 public void commit() { 83 public void commit() {
76 checkState(isOpen, TX_NOT_OPEN_ERROR); 84 checkState(isOpen, TX_NOT_OPEN_ERROR);
77 - List<UpdateOperation<String, byte[]>> allUpdates =
78 - Lists.newLinkedList();
79 try { 85 try {
86 + List<DatabaseUpdate> updates = Lists.newLinkedList();
80 txMaps.values() 87 txMaps.values()
81 - .stream() 88 + .forEach(m -> { updates.addAll(m.prepareDatabaseUpdates()); });
82 - .forEach(m -> { 89 + database.prepareAndCommit(new DefaultTransaction(transactionId, updates));
83 - allUpdates.addAll(m.prepareDatabaseUpdates()); 90 + } catch (Exception e) {
84 - }); 91 + abort();
85 - 92 + throw new TransactionException(e);
86 - if (!complete(database.atomicBatchUpdate(allUpdates))) {
87 - throw new TransactionException.OptimisticConcurrencyFailure();
88 - }
89 } finally { 93 } finally {
90 isOpen = false; 94 isOpen = false;
91 } 95 }
92 } 96 }
93 97
94 @Override 98 @Override
95 - public void rollback() { 99 + public void abort() {
96 checkState(isOpen, TX_NOT_OPEN_ERROR); 100 checkState(isOpen, TX_NOT_OPEN_ERROR);
97 - txMaps.values() 101 + txMaps.values().forEach(m -> m.rollback());
98 - .stream()
99 - .forEach(m -> m.rollback());
100 - }
101 -
102 - @Override
103 - public boolean isOpen() {
104 - return false;
105 - }
106 -
107 - private static <T> T complete(CompletableFuture<T> future) {
108 - try {
109 - return future.get(TRANSACTION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
110 - } catch (InterruptedException e) {
111 - Thread.currentThread().interrupt();
112 - throw new TransactionException.Interrupted();
113 - } catch (TimeoutException e) {
114 - throw new TransactionException.Timeout();
115 - } catch (ExecutionException e) {
116 - throw new TransactionException(e.getCause());
117 - }
118 } 102 }
119 } 103 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -16,23 +16,24 @@ ...@@ -16,23 +16,24 @@
16 16
17 package org.onosproject.store.consistent.impl; 17 package org.onosproject.store.consistent.impl;
18 18
19 -import java.util.Collection;
20 import java.util.List; 19 import java.util.List;
21 import java.util.Map; 20 import java.util.Map;
22 -import java.util.Map.Entry;
23 -import java.util.stream.Collectors;
24 import java.util.Set; 21 import java.util.Set;
25 22
26 import org.onlab.util.HexString; 23 import org.onlab.util.HexString;
27 import org.onosproject.store.service.ConsistentMap; 24 import org.onosproject.store.service.ConsistentMap;
25 +import org.onosproject.store.service.DatabaseUpdate;
28 import org.onosproject.store.service.Serializer; 26 import org.onosproject.store.service.Serializer;
29 import org.onosproject.store.service.TransactionContext; 27 import org.onosproject.store.service.TransactionContext;
30 import org.onosproject.store.service.TransactionalMap; 28 import org.onosproject.store.service.TransactionalMap;
31 -import org.onosproject.store.service.UpdateOperation;
32 import org.onosproject.store.service.Versioned; 29 import org.onosproject.store.service.Versioned;
33 30
34 import static com.google.common.base.Preconditions.*; 31 import static com.google.common.base.Preconditions.*;
35 32
33 +import com.google.common.base.Objects;
34 +import com.google.common.cache.CacheBuilder;
35 +import com.google.common.cache.CacheLoader;
36 +import com.google.common.cache.LoadingCache;
36 import com.google.common.collect.Lists; 37 import com.google.common.collect.Lists;
37 import com.google.common.collect.Maps; 38 import com.google.common.collect.Maps;
38 import com.google.common.collect.Sets; 39 import com.google.common.collect.Sets;
...@@ -55,6 +56,23 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> { ...@@ -55,6 +56,23 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> {
55 private final Map<K, V> writeCache = Maps.newConcurrentMap(); 56 private final Map<K, V> writeCache = Maps.newConcurrentMap();
56 private final Set<K> deleteSet = Sets.newConcurrentHashSet(); 57 private final Set<K> deleteSet = Sets.newConcurrentHashSet();
57 58
59 + private static final String ERROR_NULL_VALUE = "Null values are not allowed";
60 + private static final String ERROR_NULL_KEY = "Null key is not allowed";
61 +
62 + private final LoadingCache<K, String> keyCache = CacheBuilder.newBuilder()
63 + .softValues()
64 + .build(new CacheLoader<K, String>() {
65 +
66 + @Override
67 + public String load(K key) {
68 + return HexString.toHexString(serializer.encode(key));
69 + }
70 + });
71 +
72 + protected K dK(String key) {
73 + return serializer.decode(HexString.fromHexString(key));
74 + }
75 +
58 public DefaultTransactionalMap( 76 public DefaultTransactionalMap(
59 String name, 77 String name,
60 ConsistentMap<K, V> backingMap, 78 ConsistentMap<K, V> backingMap,
...@@ -69,15 +87,15 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> { ...@@ -69,15 +87,15 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> {
69 @Override 87 @Override
70 public V get(K key) { 88 public V get(K key) {
71 checkState(txContext.isOpen(), TX_CLOSED_ERROR); 89 checkState(txContext.isOpen(), TX_CLOSED_ERROR);
90 + checkNotNull(key, ERROR_NULL_KEY);
72 if (deleteSet.contains(key)) { 91 if (deleteSet.contains(key)) {
73 return null; 92 return null;
74 - } else if (writeCache.containsKey(key)) {
75 - return writeCache.get(key);
76 - } else {
77 - if (!readCache.containsKey(key)) {
78 - readCache.put(key, backingMap.get(key));
79 } 93 }
80 - Versioned<V> v = readCache.get(key); 94 + V latest = writeCache.get(key);
95 + if (latest != null) {
96 + return latest;
97 + } else {
98 + Versioned<V> v = readCache.computeIfAbsent(key, k -> backingMap.get(k));
81 return v != null ? v.value() : null; 99 return v != null ? v.value() : null;
82 } 100 }
83 } 101 }
...@@ -85,25 +103,31 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> { ...@@ -85,25 +103,31 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> {
85 @Override 103 @Override
86 public V put(K key, V value) { 104 public V put(K key, V value) {
87 checkState(txContext.isOpen(), TX_CLOSED_ERROR); 105 checkState(txContext.isOpen(), TX_CLOSED_ERROR);
88 - Versioned<V> original = readCache.get(key); 106 + checkNotNull(value, ERROR_NULL_VALUE);
89 - V recentUpdate = writeCache.put(key, value); 107 +
108 + V latest = get(key);
109 + writeCache.put(key, value);
90 deleteSet.remove(key); 110 deleteSet.remove(key);
91 - return recentUpdate == null ? (original != null ? original.value() : null) : recentUpdate; 111 + return latest;
92 } 112 }
93 113
94 @Override 114 @Override
95 public V remove(K key) { 115 public V remove(K key) {
96 checkState(txContext.isOpen(), TX_CLOSED_ERROR); 116 checkState(txContext.isOpen(), TX_CLOSED_ERROR);
97 - Versioned<V> original = readCache.get(key); 117 + V latest = get(key);
98 - V recentUpdate = writeCache.remove(key); 118 + if (latest != null) {
119 + writeCache.remove(key);
99 deleteSet.add(key); 120 deleteSet.add(key);
100 - return recentUpdate == null ? (original != null ? original.value() : null) : recentUpdate; 121 + }
122 + return latest;
101 } 123 }
102 124
103 @Override 125 @Override
104 public boolean remove(K key, V value) { 126 public boolean remove(K key, V value) {
105 - V currentValue = get(key); 127 + checkState(txContext.isOpen(), TX_CLOSED_ERROR);
106 - if (value.equals(currentValue)) { 128 + checkNotNull(value, ERROR_NULL_VALUE);
129 + V latest = get(key);
130 + if (Objects.equal(value, latest)) {
107 remove(key); 131 remove(key);
108 return true; 132 return true;
109 } 133 }
...@@ -112,8 +136,11 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> { ...@@ -112,8 +136,11 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> {
112 136
113 @Override 137 @Override
114 public boolean replace(K key, V oldValue, V newValue) { 138 public boolean replace(K key, V oldValue, V newValue) {
115 - V currentValue = get(key); 139 + checkState(txContext.isOpen(), TX_CLOSED_ERROR);
116 - if (oldValue.equals(currentValue)) { 140 + checkNotNull(oldValue, ERROR_NULL_VALUE);
141 + checkNotNull(newValue, ERROR_NULL_VALUE);
142 + V latest = get(key);
143 + if (Objects.equal(oldValue, latest)) {
117 put(key, newValue); 144 put(key, newValue);
118 return true; 145 return true;
119 } 146 }
...@@ -121,70 +148,25 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> { ...@@ -121,70 +148,25 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> {
121 } 148 }
122 149
123 @Override 150 @Override
124 - public int size() {
125 - // TODO
126 - throw new UnsupportedOperationException();
127 - }
128 -
129 - @Override
130 - public boolean isEmpty() {
131 - return size() == 0;
132 - }
133 -
134 - @Override
135 - public boolean containsKey(K key) {
136 - return get(key) != null;
137 - }
138 -
139 - @Override
140 - public boolean containsValue(V value) {
141 - // TODO
142 - throw new UnsupportedOperationException();
143 - }
144 -
145 - @Override
146 - public void clear() {
147 - // TODO
148 - throw new UnsupportedOperationException();
149 - }
150 -
151 - @Override
152 - public Set<K> keySet() {
153 - // TODO
154 - throw new UnsupportedOperationException();
155 - }
156 -
157 - @Override
158 - public Collection<V> values() {
159 - // TODO
160 - throw new UnsupportedOperationException();
161 - }
162 -
163 - @Override
164 - public Set<Entry<K, V>> entrySet() {
165 - // TODO
166 - throw new UnsupportedOperationException();
167 - }
168 -
169 - @Override
170 public V putIfAbsent(K key, V value) { 151 public V putIfAbsent(K key, V value) {
171 - V currentValue = get(key); 152 + checkState(txContext.isOpen(), TX_CLOSED_ERROR);
172 - if (currentValue == null) { 153 + checkNotNull(value, ERROR_NULL_VALUE);
154 + V latest = get(key);
155 + if (latest == null) {
173 put(key, value); 156 put(key, value);
174 - return null;
175 } 157 }
176 - return currentValue; 158 + return latest;
177 } 159 }
178 160
179 - protected List<UpdateOperation<String, byte[]>> prepareDatabaseUpdates() { 161 + protected List<DatabaseUpdate> prepareDatabaseUpdates() {
180 - List<UpdateOperation<K, V>> updates = Lists.newLinkedList(); 162 + List<DatabaseUpdate> updates = Lists.newLinkedList();
181 deleteSet.forEach(key -> { 163 deleteSet.forEach(key -> {
182 Versioned<V> original = readCache.get(key); 164 Versioned<V> original = readCache.get(key);
183 if (original != null) { 165 if (original != null) {
184 - updates.add(UpdateOperation.<K, V>newBuilder() 166 + updates.add(DatabaseUpdate.newBuilder()
185 .withTableName(name) 167 .withTableName(name)
186 - .withType(UpdateOperation.Type.REMOVE_IF_VERSION_MATCH) 168 + .withType(DatabaseUpdate.Type.REMOVE_IF_VERSION_MATCH)
187 - .withKey(key) 169 + .withKey(keyCache.getUnchecked(key))
188 .withCurrentVersion(original.version()) 170 .withCurrentVersion(original.version())
189 .build()); 171 .build());
190 } 172 }
...@@ -192,44 +174,23 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> { ...@@ -192,44 +174,23 @@ public class DefaultTransactionalMap<K, V> implements TransactionalMap<K, V> {
192 writeCache.forEach((key, value) -> { 174 writeCache.forEach((key, value) -> {
193 Versioned<V> original = readCache.get(key); 175 Versioned<V> original = readCache.get(key);
194 if (original == null) { 176 if (original == null) {
195 - updates.add(UpdateOperation.<K, V>newBuilder() 177 + updates.add(DatabaseUpdate.newBuilder()
196 .withTableName(name) 178 .withTableName(name)
197 - .withType(UpdateOperation.Type.PUT_IF_ABSENT) 179 + .withType(DatabaseUpdate.Type.PUT_IF_ABSENT)
198 - .withKey(key) 180 + .withKey(keyCache.getUnchecked(key))
199 - .withValue(value) 181 + .withValue(serializer.encode(value))
200 .build()); 182 .build());
201 } else { 183 } else {
202 - updates.add(UpdateOperation.<K, V>newBuilder() 184 + updates.add(DatabaseUpdate.newBuilder()
203 .withTableName(name) 185 .withTableName(name)
204 - .withType(UpdateOperation.Type.PUT_IF_VERSION_MATCH) 186 + .withType(DatabaseUpdate.Type.PUT_IF_VERSION_MATCH)
205 - .withKey(key) 187 + .withKey(keyCache.getUnchecked(key))
206 .withCurrentVersion(original.version()) 188 .withCurrentVersion(original.version())
207 - .withValue(value) 189 + .withValue(serializer.encode(value))
208 .build()); 190 .build());
209 } 191 }
210 }); 192 });
211 - return updates.stream().map(this::toRawUpdateOperation).collect(Collectors.toList()); 193 + return updates;
212 - }
213 -
214 - private UpdateOperation<String, byte[]> toRawUpdateOperation(UpdateOperation<K, V> update) {
215 -
216 - UpdateOperation.Builder<String, byte[]> rawUpdate = UpdateOperation.<String, byte[]>newBuilder();
217 -
218 - rawUpdate = rawUpdate.withKey(HexString.toHexString(serializer.encode(update.key())))
219 - .withCurrentVersion(update.currentVersion())
220 - .withType(update.type());
221 -
222 - rawUpdate = rawUpdate.withTableName(update.tableName());
223 -
224 - if (update.value() != null) {
225 - rawUpdate = rawUpdate.withValue(serializer.encode(update.value()));
226 - }
227 -
228 - if (update.currentValue() != null) {
229 - rawUpdate = rawUpdate.withCurrentValue(serializer.encode(update.currentValue()));
230 - }
231 -
232 - return rawUpdate.build();
233 } 194 }
234 195
235 /** 196 /**
......
...@@ -27,7 +27,8 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -27,7 +27,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
27 import java.util.concurrent.atomic.AtomicInteger; 27 import java.util.concurrent.atomic.AtomicInteger;
28 import java.util.stream.Collectors; 28 import java.util.stream.Collectors;
29 29
30 -import org.onosproject.store.service.UpdateOperation; 30 +import org.onosproject.store.service.DatabaseUpdate;
31 +import org.onosproject.store.service.Transaction;
31 import org.onosproject.store.service.Versioned; 32 import org.onosproject.store.service.Versioned;
32 33
33 import com.google.common.collect.Lists; 34 import com.google.common.collect.Lists;
...@@ -129,24 +130,27 @@ public class PartitionedDatabase implements Database { ...@@ -129,24 +130,27 @@ public class PartitionedDatabase implements Database {
129 } 130 }
130 131
131 @Override 132 @Override
132 - public CompletableFuture<Versioned<byte[]>> put(String tableName, String key, byte[] value) { 133 + public CompletableFuture<Result<Versioned<byte[]>>> put(String tableName, String key, byte[] value) {
133 checkState(isOpen.get(), DB_NOT_OPEN); 134 checkState(isOpen.get(), DB_NOT_OPEN);
134 return partitioner.getPartition(tableName, key).put(tableName, key, value); 135 return partitioner.getPartition(tableName, key).put(tableName, key, value);
135 } 136 }
136 137
137 @Override 138 @Override
138 - public CompletableFuture<Versioned<byte[]>> remove(String tableName, String key) { 139 + public CompletableFuture<Result<Versioned<byte[]>>> remove(String tableName, String key) {
139 checkState(isOpen.get(), DB_NOT_OPEN); 140 checkState(isOpen.get(), DB_NOT_OPEN);
140 return partitioner.getPartition(tableName, key).remove(tableName, key); 141 return partitioner.getPartition(tableName, key).remove(tableName, key);
141 } 142 }
142 143
143 @Override 144 @Override
144 - public CompletableFuture<Void> clear(String tableName) { 145 + public CompletableFuture<Result<Void>> clear(String tableName) {
146 + AtomicBoolean isLocked = new AtomicBoolean(false);
145 checkState(isOpen.get(), DB_NOT_OPEN); 147 checkState(isOpen.get(), DB_NOT_OPEN);
146 return CompletableFuture.allOf(partitions 148 return CompletableFuture.allOf(partitions
147 .stream() 149 .stream()
148 - .map(p -> p.clear(tableName)) 150 + .map(p -> p.clear(tableName)
149 - .toArray(CompletableFuture[]::new)); 151 + .thenApply(v -> isLocked.compareAndSet(false, Result.Status.LOCKED == v.status())))
152 + .toArray(CompletableFuture[]::new))
153 + .thenApply(v -> isLocked.get() ? Result.locked() : Result.ok(null));
150 } 154 }
151 155
152 @Override 156 @Override
...@@ -183,56 +187,83 @@ public class PartitionedDatabase implements Database { ...@@ -183,56 +187,83 @@ public class PartitionedDatabase implements Database {
183 } 187 }
184 188
185 @Override 189 @Override
186 - public CompletableFuture<Versioned<byte[]>> putIfAbsent(String tableName, String key, byte[] value) { 190 + public CompletableFuture<Result<Versioned<byte[]>>> putIfAbsent(String tableName, String key, byte[] value) {
187 checkState(isOpen.get(), DB_NOT_OPEN); 191 checkState(isOpen.get(), DB_NOT_OPEN);
188 return partitioner.getPartition(tableName, key).putIfAbsent(tableName, key, value); 192 return partitioner.getPartition(tableName, key).putIfAbsent(tableName, key, value);
189 } 193 }
190 194
191 @Override 195 @Override
192 - public CompletableFuture<Boolean> remove(String tableName, String key, byte[] value) { 196 + public CompletableFuture<Result<Boolean>> remove(String tableName, String key, byte[] value) {
193 checkState(isOpen.get(), DB_NOT_OPEN); 197 checkState(isOpen.get(), DB_NOT_OPEN);
194 return partitioner.getPartition(tableName, key).remove(tableName, key, value); 198 return partitioner.getPartition(tableName, key).remove(tableName, key, value);
195 } 199 }
196 200
197 @Override 201 @Override
198 - public CompletableFuture<Boolean> remove(String tableName, String key, long version) { 202 + public CompletableFuture<Result<Boolean>> remove(String tableName, String key, long version) {
199 checkState(isOpen.get(), DB_NOT_OPEN); 203 checkState(isOpen.get(), DB_NOT_OPEN);
200 return partitioner.getPartition(tableName, key).remove(tableName, key, version); 204 return partitioner.getPartition(tableName, key).remove(tableName, key, version);
201 } 205 }
202 206
203 @Override 207 @Override
204 - public CompletableFuture<Boolean> replace(String tableName, String key, byte[] oldValue, byte[] newValue) { 208 + public CompletableFuture<Result<Boolean>> replace(
209 + String tableName, String key, byte[] oldValue, byte[] newValue) {
205 checkState(isOpen.get(), DB_NOT_OPEN); 210 checkState(isOpen.get(), DB_NOT_OPEN);
206 return partitioner.getPartition(tableName, key).replace(tableName, key, oldValue, newValue); 211 return partitioner.getPartition(tableName, key).replace(tableName, key, oldValue, newValue);
207 } 212 }
208 213
209 @Override 214 @Override
210 - public CompletableFuture<Boolean> replace(String tableName, String key, long oldVersion, byte[] newValue) { 215 + public CompletableFuture<Result<Boolean>> replace(
216 + String tableName, String key, long oldVersion, byte[] newValue) {
211 checkState(isOpen.get(), DB_NOT_OPEN); 217 checkState(isOpen.get(), DB_NOT_OPEN);
212 return partitioner.getPartition(tableName, key).replace(tableName, key, oldVersion, newValue); 218 return partitioner.getPartition(tableName, key).replace(tableName, key, oldVersion, newValue);
213 } 219 }
214 220
215 @Override 221 @Override
216 - public CompletableFuture<Boolean> atomicBatchUpdate(List<UpdateOperation<String, byte[]>> updates) { 222 + public CompletableFuture<Boolean> prepareAndCommit(Transaction transaction) {
217 - checkState(isOpen.get(), DB_NOT_OPEN); 223 + Map<Database, Transaction> subTransactions = createSubTransactions(transaction);
218 - Map<Database, List<UpdateOperation<String, byte[]>>> perPartitionUpdates = Maps.newHashMap(); 224 + if (subTransactions.isEmpty()) {
219 - for (UpdateOperation<String, byte[]> update : updates) { 225 + return CompletableFuture.completedFuture(true);
220 - Database partition = partitioner.getPartition(update.tableName(), update.key()); 226 + } else if (subTransactions.size() == 1) {
221 - List<UpdateOperation<String, byte[]>> partitionUpdates = perPartitionUpdates.get(partition); 227 + Entry<Database, Transaction> entry =
222 - if (partitionUpdates == null) { 228 + subTransactions.entrySet().iterator().next();
223 - partitionUpdates = Lists.newArrayList(); 229 + return entry.getKey().prepareAndCommit(entry.getValue());
224 - perPartitionUpdates.put(partition, partitionUpdates); 230 + } else {
231 + return new TransactionManager(this).execute(transaction);
225 } 232 }
226 - partitionUpdates.add(update);
227 } 233 }
228 - if (perPartitionUpdates.size() > 1) { 234 +
229 - // TODO 235 + @Override
230 - throw new UnsupportedOperationException("Cross partition transactional updates are not supported."); 236 + public CompletableFuture<Boolean> prepare(Transaction transaction) {
231 - } else { 237 + Map<Database, Transaction> subTransactions = createSubTransactions(transaction);
232 - Entry<Database, List<UpdateOperation<String, byte[]>>> only = 238 + AtomicBoolean status = new AtomicBoolean(true);
233 - perPartitionUpdates.entrySet().iterator().next(); 239 + return CompletableFuture.allOf(subTransactions.entrySet()
234 - return only.getKey().atomicBatchUpdate(only.getValue()); 240 + .stream()
241 + .map(entry -> entry
242 + .getKey()
243 + .prepare(entry.getValue())
244 + .thenApply(v -> status.compareAndSet(true, v)))
245 + .toArray(CompletableFuture[]::new))
246 + .thenApply(v -> status.get());
247 + }
248 +
249 + @Override
250 + public CompletableFuture<Boolean> commit(Transaction transaction) {
251 + Map<Database, Transaction> subTransactions = createSubTransactions(transaction);
252 + return CompletableFuture.allOf(subTransactions.entrySet()
253 + .stream()
254 + .map(entry -> entry.getKey().commit(entry.getValue()))
255 + .toArray(CompletableFuture[]::new))
256 + .thenApply(v -> true);
235 } 257 }
258 +
259 + @Override
260 + public CompletableFuture<Boolean> rollback(Transaction transaction) {
261 + Map<Database, Transaction> subTransactions = createSubTransactions(transaction);
262 + return CompletableFuture.allOf(subTransactions.entrySet()
263 + .stream()
264 + .map(entry -> entry.getKey().rollback(entry.getValue()))
265 + .toArray(CompletableFuture[]::new))
266 + .thenApply(v -> true);
236 } 267 }
237 268
238 @Override 269 @Override
...@@ -243,7 +274,8 @@ public class PartitionedDatabase implements Database { ...@@ -243,7 +274,8 @@ public class PartitionedDatabase implements Database {
243 .toArray(CompletableFuture[]::new)) 274 .toArray(CompletableFuture[]::new))
244 .thenApply(v -> { 275 .thenApply(v -> {
245 isOpen.set(true); 276 isOpen.set(true);
246 - return this; }); 277 + return this;
278 + });
247 } 279 }
248 280
249 @Override 281 @Override
...@@ -279,4 +311,19 @@ public class PartitionedDatabase implements Database { ...@@ -279,4 +311,19 @@ public class PartitionedDatabase implements Database {
279 public Database addShutdownTask(Task<CompletableFuture<Void>> task) { 311 public Database addShutdownTask(Task<CompletableFuture<Void>> task) {
280 throw new UnsupportedOperationException(); 312 throw new UnsupportedOperationException();
281 } 313 }
314 +
315 + private Map<Database, Transaction> createSubTransactions(
316 + Transaction transaction) {
317 + Map<Database, List<DatabaseUpdate>> perPartitionUpdates = Maps.newHashMap();
318 + for (DatabaseUpdate update : transaction.updates()) {
319 + Database partition = partitioner.getPartition(update.tableName(), update.key());
320 + List<DatabaseUpdate> partitionUpdates =
321 + perPartitionUpdates.computeIfAbsent(partition, k -> Lists.newLinkedList());
322 + partitionUpdates.add(update);
323 + }
324 + Map<Database, Transaction> subTransactions = Maps.newHashMap();
325 + perPartitionUpdates.forEach((k, v) -> subTransactions.put(k, new DefaultTransaction(transaction.id(), v)));
326 +
327 + return subTransactions;
328 + }
282 } 329 }
......
1 +/*
2 + * Copyright 2015 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 +package org.onosproject.store.consistent.impl;
17 +
18 +/**
19 + * Result of a database update operation.
20 + *
21 + * @param <V> return value type
22 + */
23 +public final class Result<V> {
24 +
25 + public enum Status {
26 + /**
27 + * Indicates a successful update.
28 + */
29 + OK,
30 +
31 + /**
32 + * Indicates a failure due to underlying state being locked by another transaction.
33 + */
34 + LOCKED
35 + }
36 +
37 + private final Status status;
38 + private final V value;
39 +
40 + /**
41 + * Creates a new Result instance with the specified value with status set to Status.OK.
42 + *
43 + * @param <V> result value type
44 + * @param value result value
45 + * @return Result instance
46 + */
47 + public static <V> Result<V> ok(V value) {
48 + return new Result<>(value, Status.OK);
49 + }
50 +
51 + /**
52 + * Creates a new Result instance with status set to Status.LOCKED.
53 + *
54 + * @param <V> result value type
55 + * @return Result instance
56 + */
57 + public static <V> Result<V> locked() {
58 + return new Result<>(null, Status.LOCKED);
59 + }
60 +
61 + private Result(V value, Status status) {
62 + this.value = value;
63 + this.status = status;
64 + }
65 +
66 + /**
67 + * Returns true if this result indicates a successful execution i.e status is Status.OK.
68 + *
69 + * @return true if successful, false otherwise
70 + */
71 + public boolean success() {
72 + return status == Status.OK;
73 + }
74 +
75 + /**
76 + * Returns the status of database update operation.
77 + * @return database update status
78 + */
79 + public Status status() {
80 + return status;
81 + }
82 +
83 + /**
84 + * Returns the return value for the update.
85 + * @return value returned by database update. If the status is another
86 + * other than Status.OK, this returns a null
87 + */
88 + public V value() {
89 + return value;
90 + }
91 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2015 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 +package org.onosproject.store.consistent.impl;
17 +
18 +import static com.google.common.base.Preconditions.checkNotNull;
19 +import java.util.Collection;
20 +import java.util.concurrent.CompletableFuture;
21 +import java.util.stream.Collectors;
22 +
23 +import org.apache.commons.lang3.tuple.ImmutablePair;
24 +import org.apache.commons.lang3.tuple.Pair;
25 +import org.onlab.util.KryoNamespace;
26 +import org.onosproject.store.serializers.KryoNamespaces;
27 +import org.onosproject.store.service.AsyncConsistentMap;
28 +import org.onosproject.store.service.DatabaseUpdate;
29 +import org.onosproject.store.service.Serializer;
30 +import org.onosproject.store.service.Transaction;
31 +import org.onosproject.store.service.Versioned;
32 +import org.onosproject.store.service.Transaction.State;
33 +
34 +/**
35 + * Agent that runs the two phase commit protocol.
36 + */
37 +public class TransactionManager {
38 +
39 + private final Database database;
40 + private final AsyncConsistentMap<Long, Transaction> transactions;
41 +
42 + private final Serializer serializer = new Serializer() {
43 +
44 + private KryoNamespace kryo = KryoNamespace.newBuilder()
45 + .register(KryoNamespaces.BASIC)
46 + .nextId(KryoNamespace.FLOATING_ID)
47 + .register(Versioned.class)
48 + .register(DatabaseUpdate.class)
49 + .register(DatabaseUpdate.Type.class)
50 + .register(DefaultTransaction.class)
51 + .register(Transaction.State.class)
52 + .register(Pair.class)
53 + .register(ImmutablePair.class)
54 + .build();
55 +
56 + @Override
57 + public <T> byte[] encode(T object) {
58 + return kryo.serialize(object);
59 + }
60 +
61 + @Override
62 + public <T> T decode(byte[] bytes) {
63 + return kryo.deserialize(bytes);
64 + }
65 + };
66 +
67 + /**
68 + * Constructs a new TransactionManager for the specified database instance.
69 + *
70 + * @param database database
71 + */
72 + public TransactionManager(Database database) {
73 + this.database = checkNotNull(database, "database cannot be null");
74 + this.transactions = new DefaultAsyncConsistentMap<>("onos-transactions", this.database, serializer);
75 + }
76 +
77 + /**
78 + * Executes the specified transaction by employing a two phase commit protocol.
79 + *
80 + * @param transaction transaction to commit
81 + * @return transaction result. Result value true indicates a successful commit, false
82 + * indicates abort
83 + */
84 + public CompletableFuture<Boolean> execute(Transaction transaction) {
85 + // clean up if this transaction in already in a terminal state.
86 + if (transaction.state() == Transaction.State.COMMITTED ||
87 + transaction.state() == Transaction.State.ROLLEDBACK) {
88 + return transactions.remove(transaction.id()).thenApply(v -> true);
89 + } else if (transaction.state() == Transaction.State.COMMITTING) {
90 + return commit(transaction);
91 + } else if (transaction.state() == Transaction.State.ROLLINGBACK) {
92 + return rollback(transaction);
93 + } else {
94 + return prepare(transaction).thenCompose(v -> v ? commit(transaction) : rollback(transaction));
95 + }
96 + }
97 +
98 +
99 + /**
100 + * Returns all transactions in the system.
101 + *
102 + * @return future for a collection of transactions
103 + */
104 + public CompletableFuture<Collection<Transaction>> getTransactions() {
105 + return transactions.values().thenApply(c -> {
106 + Collection<Transaction> txns = c.stream().map(v -> v.value()).collect(Collectors.toList());
107 + return txns;
108 + });
109 + }
110 +
111 + private CompletableFuture<Boolean> prepare(Transaction transaction) {
112 + return transactions.put(transaction.id(), transaction)
113 + .thenCompose(v -> database.prepare(transaction))
114 + .thenCompose(status -> transactions.put(
115 + transaction.id(),
116 + transaction.transition(status ? State.COMMITTING : State.ROLLINGBACK))
117 + .thenApply(v -> status));
118 + }
119 +
120 + private CompletableFuture<Boolean> commit(Transaction transaction) {
121 + return database.commit(transaction)
122 + .thenCompose(v -> transactions.put(
123 + transaction.id(),
124 + transaction.transition(Transaction.State.COMMITTED)))
125 + .thenApply(v -> true);
126 + }
127 +
128 + private CompletableFuture<Boolean> rollback(Transaction transaction) {
129 + return database.rollback(transaction)
130 + .thenCompose(v -> transactions.put(
131 + transaction.id(),
132 + transaction.transition(Transaction.State.ROLLEDBACK)))
133 + .thenApply(v -> true);
134 + }
135 +}
...\ No newline at end of file ...\ No newline at end of file