Committed by
Ray Milkey
Added distributed transaction support through a two phase commit protocol
Change-Id: I85d64234a24823fee8b3c2ea830abbb6867dad38
Showing
25 changed files
with
1136 additions
and
462 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,43 +18,58 @@ package org.onosproject.store.consistent.impl; | ... | @@ -18,43 +18,58 @@ package org.onosproject.store.consistent.impl; |
18 | 18 | ||
19 | import java.util.Arrays; | 19 | import java.util.Arrays; |
20 | import java.util.Collection; | 20 | import java.util.Collection; |
21 | -import java.util.HashMap; | ||
22 | import java.util.HashSet; | 21 | import java.util.HashSet; |
23 | -import java.util.List; | ||
24 | import java.util.Map; | 22 | import java.util.Map; |
25 | import java.util.Map.Entry; | 23 | import java.util.Map.Entry; |
26 | import java.util.stream.Collectors; | 24 | import java.util.stream.Collectors; |
27 | import java.util.Set; | 25 | import java.util.Set; |
28 | 26 | ||
29 | import org.apache.commons.lang3.tuple.Pair; | 27 | import org.apache.commons.lang3.tuple.Pair; |
30 | -import org.onosproject.store.service.UpdateOperation; | 28 | +import org.onosproject.store.service.DatabaseUpdate; |
29 | +import org.onosproject.store.service.Transaction; | ||
31 | import org.onosproject.store.service.Versioned; | 30 | import org.onosproject.store.service.Versioned; |
31 | +import org.onosproject.store.service.DatabaseUpdate.Type; | ||
32 | 32 | ||
33 | +import com.google.common.base.Objects; | ||
33 | import com.google.common.collect.ImmutableList; | 34 | import com.google.common.collect.ImmutableList; |
34 | import com.google.common.collect.ImmutableSet; | 35 | import com.google.common.collect.ImmutableSet; |
36 | +import com.google.common.collect.Maps; | ||
35 | 37 | ||
36 | import net.kuujo.copycat.state.Initializer; | 38 | import net.kuujo.copycat.state.Initializer; |
37 | import net.kuujo.copycat.state.StateContext; | 39 | import net.kuujo.copycat.state.StateContext; |
38 | 40 | ||
39 | /** | 41 | /** |
40 | * Default database state. | 42 | * Default database state. |
41 | - * | ||
42 | - * @param <K> key type | ||
43 | - * @param <V> value type | ||
44 | */ | 43 | */ |
45 | -public class DefaultDatabaseState<K, V> implements DatabaseState<K, V> { | 44 | +public class DefaultDatabaseState implements DatabaseState<String, byte[]> { |
46 | - | ||
47 | private Long nextVersion; | 45 | private Long nextVersion; |
48 | - private Map<String, Map<K, Versioned<V>>> tables; | 46 | + private Map<String, Map<String, Versioned<byte[]>>> tables; |
47 | + | ||
48 | + /** | ||
49 | + * This locks map has a structure similar to the "tables" map above and | ||
50 | + * holds all the provisional updates made during a transaction's prepare phase. | ||
51 | + * The entry value is represented as the tuple: (transactionId, newValue) | ||
52 | + * If newValue == null that signifies this update is attempting to | ||
53 | + * delete the existing value. | ||
54 | + * This map also serves as a lock on the entries that are being updated. | ||
55 | + * The presence of a entry in this map indicates that element is | ||
56 | + * participating in a transaction and is currently locked for updates. | ||
57 | + */ | ||
58 | + private Map<String, Map<String, Pair<Long, byte[]>>> locks; | ||
49 | 59 | ||
50 | @Initializer | 60 | @Initializer |
51 | @Override | 61 | @Override |
52 | - public void init(StateContext<DatabaseState<K, V>> context) { | 62 | + public void init(StateContext<DatabaseState<String, byte[]>> context) { |
53 | tables = context.get("tables"); | 63 | tables = context.get("tables"); |
54 | if (tables == null) { | 64 | if (tables == null) { |
55 | - tables = new HashMap<>(); | 65 | + tables = Maps.newConcurrentMap(); |
56 | context.put("tables", tables); | 66 | context.put("tables", tables); |
57 | } | 67 | } |
68 | + locks = context.get("locks"); | ||
69 | + if (locks == null) { | ||
70 | + locks = Maps.newConcurrentMap(); | ||
71 | + context.put("locks", locks); | ||
72 | + } | ||
58 | nextVersion = context.get("nextVersion"); | 73 | nextVersion = context.get("nextVersion"); |
59 | if (nextVersion == null) { | 74 | if (nextVersion == null) { |
60 | nextVersion = new Long(0); | 75 | nextVersion = new Long(0); |
... | @@ -62,15 +77,6 @@ public class DefaultDatabaseState<K, V> implements DatabaseState<K, V> { | ... | @@ -62,15 +77,6 @@ public class DefaultDatabaseState<K, V> implements DatabaseState<K, V> { |
62 | } | 77 | } |
63 | } | 78 | } |
64 | 79 | ||
65 | - private Map<K, Versioned<V>> getTableMap(String tableName) { | ||
66 | - Map<K, Versioned<V>> table = tables.get(tableName); | ||
67 | - if (table == null) { | ||
68 | - table = new HashMap<>(); | ||
69 | - tables.put(tableName, table); | ||
70 | - } | ||
71 | - return table; | ||
72 | - } | ||
73 | - | ||
74 | @Override | 80 | @Override |
75 | public Set<String> tableNames() { | 81 | public Set<String> tableNames() { |
76 | return new HashSet<>(tables.keySet()); | 82 | return new HashSet<>(tables.keySet()); |
... | @@ -87,47 +93,55 @@ public class DefaultDatabaseState<K, V> implements DatabaseState<K, V> { | ... | @@ -87,47 +93,55 @@ public class DefaultDatabaseState<K, V> implements DatabaseState<K, V> { |
87 | } | 93 | } |
88 | 94 | ||
89 | @Override | 95 | @Override |
90 | - public boolean containsKey(String tableName, K key) { | 96 | + public boolean containsKey(String tableName, String key) { |
91 | return getTableMap(tableName).containsKey(key); | 97 | return getTableMap(tableName).containsKey(key); |
92 | } | 98 | } |
93 | 99 | ||
94 | @Override | 100 | @Override |
95 | - public boolean containsValue(String tableName, V value) { | 101 | + public boolean containsValue(String tableName, byte[] value) { |
96 | - return getTableMap(tableName).values().stream().anyMatch(v -> checkEquality(v.value(), value)); | 102 | + return getTableMap(tableName).values().stream().anyMatch(v -> Arrays.equals(v.value(), value)); |
97 | } | 103 | } |
98 | 104 | ||
99 | @Override | 105 | @Override |
100 | - public Versioned<V> get(String tableName, K key) { | 106 | + public Versioned<byte[]> get(String tableName, String key) { |
101 | return getTableMap(tableName).get(key); | 107 | return getTableMap(tableName).get(key); |
102 | } | 108 | } |
103 | 109 | ||
104 | @Override | 110 | @Override |
105 | - public Versioned<V> put(String tableName, K key, V value) { | 111 | + public Result<Versioned<byte[]>> put(String tableName, String key, byte[] value) { |
106 | - return getTableMap(tableName).put(key, new Versioned<>(value, ++nextVersion)); | 112 | + return isLockedForUpdates(tableName, key) |
113 | + ? Result.locked() | ||
114 | + : Result.ok(getTableMap(tableName).put(key, new Versioned<>(value, ++nextVersion))); | ||
107 | } | 115 | } |
108 | 116 | ||
109 | @Override | 117 | @Override |
110 | - public Versioned<V> remove(String tableName, K key) { | 118 | + public Result<Versioned<byte[]>> remove(String tableName, String key) { |
111 | - return getTableMap(tableName).remove(key); | 119 | + return isLockedForUpdates(tableName, key) |
120 | + ? Result.locked() | ||
121 | + : Result.ok(getTableMap(tableName).remove(key)); | ||
112 | } | 122 | } |
113 | 123 | ||
114 | @Override | 124 | @Override |
115 | - public void clear(String tableName) { | 125 | + public Result<Void> clear(String tableName) { |
126 | + if (areTransactionsInProgress(tableName)) { | ||
127 | + return Result.locked(); | ||
128 | + } | ||
116 | getTableMap(tableName).clear(); | 129 | getTableMap(tableName).clear(); |
130 | + return Result.ok(null); | ||
117 | } | 131 | } |
118 | 132 | ||
119 | @Override | 133 | @Override |
120 | - public Set<K> keySet(String tableName) { | 134 | + public Set<String> keySet(String tableName) { |
121 | return ImmutableSet.copyOf(getTableMap(tableName).keySet()); | 135 | return ImmutableSet.copyOf(getTableMap(tableName).keySet()); |
122 | } | 136 | } |
123 | 137 | ||
124 | @Override | 138 | @Override |
125 | - public Collection<Versioned<V>> values(String tableName) { | 139 | + public Collection<Versioned<byte[]>> values(String tableName) { |
126 | return ImmutableList.copyOf(getTableMap(tableName).values()); | 140 | return ImmutableList.copyOf(getTableMap(tableName).values()); |
127 | } | 141 | } |
128 | 142 | ||
129 | @Override | 143 | @Override |
130 | - public Set<Entry<K, Versioned<V>>> entrySet(String tableName) { | 144 | + public Set<Entry<String, Versioned<byte[]>>> entrySet(String tableName) { |
131 | return ImmutableSet.copyOf(getTableMap(tableName) | 145 | return ImmutableSet.copyOf(getTableMap(tableName) |
132 | .entrySet() | 146 | .entrySet() |
133 | .stream() | 147 | .stream() |
... | @@ -136,116 +150,201 @@ public class DefaultDatabaseState<K, V> implements DatabaseState<K, V> { | ... | @@ -136,116 +150,201 @@ public class DefaultDatabaseState<K, V> implements DatabaseState<K, V> { |
136 | } | 150 | } |
137 | 151 | ||
138 | @Override | 152 | @Override |
139 | - public Versioned<V> putIfAbsent(String tableName, K key, V value) { | 153 | + public Result<Versioned<byte[]>> putIfAbsent(String tableName, String key, byte[] value) { |
140 | - Versioned<V> existingValue = getTableMap(tableName).get(key); | 154 | + if (isLockedForUpdates(tableName, key)) { |
141 | - return existingValue != null ? existingValue : put(tableName, key, value); | 155 | + return Result.locked(); |
156 | + } | ||
157 | + Versioned<byte[]> existingValue = get(tableName, key); | ||
158 | + Versioned<byte[]> currentValue = existingValue != null ? existingValue : put(tableName, key, value).value(); | ||
159 | + return Result.ok(currentValue); | ||
142 | } | 160 | } |
143 | 161 | ||
144 | @Override | 162 | @Override |
145 | - public boolean remove(String tableName, K key, V value) { | 163 | + public Result<Boolean> remove(String tableName, String key, byte[] value) { |
146 | - Versioned<V> existing = getTableMap(tableName).get(key); | 164 | + if (isLockedForUpdates(tableName, key)) { |
147 | - if (existing != null && checkEquality(existing.value(), value)) { | 165 | + return Result.locked(); |
166 | + } | ||
167 | + Versioned<byte[]> existing = get(tableName, key); | ||
168 | + if (existing != null && Arrays.equals(existing.value(), value)) { | ||
148 | getTableMap(tableName).remove(key); | 169 | getTableMap(tableName).remove(key); |
149 | - return true; | 170 | + return Result.ok(true); |
150 | } | 171 | } |
151 | - return false; | 172 | + return Result.ok(false); |
152 | } | 173 | } |
153 | 174 | ||
154 | @Override | 175 | @Override |
155 | - public boolean remove(String tableName, K key, long version) { | 176 | + public Result<Boolean> remove(String tableName, String key, long version) { |
156 | - Versioned<V> existing = getTableMap(tableName).get(key); | 177 | + if (isLockedForUpdates(tableName, key)) { |
178 | + return Result.locked(); | ||
179 | + } | ||
180 | + Versioned<byte[]> existing = get(tableName, key); | ||
157 | if (existing != null && existing.version() == version) { | 181 | if (existing != null && existing.version() == version) { |
158 | remove(tableName, key); | 182 | remove(tableName, key); |
159 | - return true; | 183 | + return Result.ok(true); |
160 | } | 184 | } |
161 | - return false; | 185 | + return Result.ok(false); |
162 | } | 186 | } |
163 | 187 | ||
164 | @Override | 188 | @Override |
165 | - public boolean replace(String tableName, K key, V oldValue, V newValue) { | 189 | + public Result<Boolean> replace(String tableName, String key, byte[] oldValue, byte[] newValue) { |
166 | - Versioned<V> existing = getTableMap(tableName).get(key); | 190 | + if (isLockedForUpdates(tableName, key)) { |
167 | - if (existing != null && checkEquality(existing.value(), oldValue)) { | 191 | + return Result.locked(); |
192 | + } | ||
193 | + Versioned<byte[]> existing = get(tableName, key); | ||
194 | + if (existing != null && Arrays.equals(existing.value(), oldValue)) { | ||
168 | put(tableName, key, newValue); | 195 | put(tableName, key, newValue); |
169 | - return true; | 196 | + return Result.ok(true); |
170 | } | 197 | } |
171 | - return false; | 198 | + return Result.ok(false); |
172 | } | 199 | } |
173 | 200 | ||
174 | @Override | 201 | @Override |
175 | - public boolean replace(String tableName, K key, long oldVersion, V newValue) { | 202 | + public Result<Boolean> replace(String tableName, String key, long oldVersion, byte[] newValue) { |
176 | - Versioned<V> existing = getTableMap(tableName).get(key); | 203 | + if (isLockedForUpdates(tableName, key)) { |
204 | + return Result.locked(); | ||
205 | + } | ||
206 | + Versioned<byte[]> existing = get(tableName, key); | ||
177 | if (existing != null && existing.version() == oldVersion) { | 207 | if (existing != null && existing.version() == oldVersion) { |
178 | put(tableName, key, newValue); | 208 | put(tableName, key, newValue); |
179 | - return true; | 209 | + return Result.ok(true); |
210 | + } | ||
211 | + return Result.ok(false); | ||
212 | + } | ||
213 | + | ||
214 | + @Override | ||
215 | + public boolean prepareAndCommit(Transaction transaction) { | ||
216 | + if (prepare(transaction)) { | ||
217 | + return commit(transaction); | ||
180 | } | 218 | } |
181 | return false; | 219 | return false; |
182 | } | 220 | } |
183 | 221 | ||
184 | @Override | 222 | @Override |
185 | - public boolean batchUpdate(List<UpdateOperation<K, V>> updates) { | 223 | + public boolean prepare(Transaction transaction) { |
186 | - if (updates.stream().anyMatch(update -> !checkIfUpdateIsPossible(update))) { | 224 | + if (transaction.updates().stream().anyMatch(update -> |
225 | + isLockedByAnotherTransaction(update.tableName(), | ||
226 | + update.key(), | ||
227 | + transaction.id()))) { | ||
187 | return false; | 228 | return false; |
188 | - } else { | 229 | + } |
189 | - updates.stream().forEach(this::doUpdate); | 230 | + |
231 | + if (transaction.updates().stream().allMatch(this::isUpdatePossible)) { | ||
232 | + transaction.updates().forEach(update -> doProvisionalUpdate(update, transaction.id())); | ||
190 | return true; | 233 | return true; |
191 | } | 234 | } |
235 | + return false; | ||
192 | } | 236 | } |
193 | 237 | ||
194 | - private void doUpdate(UpdateOperation<K, V> update) { | 238 | + @Override |
195 | - String tableName = update.tableName(); | 239 | + public boolean commit(Transaction transaction) { |
196 | - K key = update.key(); | 240 | + transaction.updates().forEach(update -> commitProvisionalUpdate(update, transaction.id())); |
241 | + return true; | ||
242 | + } | ||
243 | + | ||
244 | + @Override | ||
245 | + public boolean rollback(Transaction transaction) { | ||
246 | + transaction.updates().forEach(update -> undoProvisionalUpdate(update, transaction.id())); | ||
247 | + return true; | ||
248 | + } | ||
249 | + | ||
250 | + private Map<String, Versioned<byte[]>> getTableMap(String tableName) { | ||
251 | + return tables.computeIfAbsent(tableName, name -> Maps.newConcurrentMap()); | ||
252 | + } | ||
253 | + | ||
254 | + private Map<String, Pair<Long, byte[]>> getLockMap(String tableName) { | ||
255 | + return locks.computeIfAbsent(tableName, name -> Maps.newConcurrentMap()); | ||
256 | + } | ||
257 | + | ||
258 | + private boolean isUpdatePossible(DatabaseUpdate update) { | ||
259 | + Versioned<byte[]> existingEntry = get(update.tableName(), update.key()); | ||
197 | switch (update.type()) { | 260 | switch (update.type()) { |
198 | case PUT: | 261 | case PUT: |
199 | - put(tableName, key, update.value()); | ||
200 | - return; | ||
201 | case REMOVE: | 262 | case REMOVE: |
202 | - remove(tableName, key); | 263 | + return true; |
203 | - return; | ||
204 | case PUT_IF_ABSENT: | 264 | case PUT_IF_ABSENT: |
205 | - putIfAbsent(tableName, key, update.value()); | 265 | + return existingEntry == null; |
206 | - return; | ||
207 | case PUT_IF_VERSION_MATCH: | 266 | case PUT_IF_VERSION_MATCH: |
208 | - replace(tableName, key, update.currentValue(), update.value()); | 267 | + return existingEntry != null && existingEntry.version() == update.currentVersion(); |
209 | - return; | ||
210 | case PUT_IF_VALUE_MATCH: | 268 | case PUT_IF_VALUE_MATCH: |
211 | - replace(tableName, key, update.currentVersion(), update.value()); | 269 | + return existingEntry != null && Arrays.equals(existingEntry.value(), update.currentValue()); |
212 | - return; | ||
213 | case REMOVE_IF_VERSION_MATCH: | 270 | case REMOVE_IF_VERSION_MATCH: |
214 | - remove(tableName, key, update.currentVersion()); | 271 | + return existingEntry == null || existingEntry.version() == update.currentVersion(); |
215 | - return; | ||
216 | case REMOVE_IF_VALUE_MATCH: | 272 | case REMOVE_IF_VALUE_MATCH: |
217 | - remove(tableName, key, update.currentValue()); | 273 | + return existingEntry == null || Arrays.equals(existingEntry.value(), update.currentValue()); |
218 | - return; | ||
219 | default: | 274 | default: |
220 | throw new IllegalStateException("Unsupported type: " + update.type()); | 275 | throw new IllegalStateException("Unsupported type: " + update.type()); |
221 | } | 276 | } |
222 | } | 277 | } |
223 | 278 | ||
224 | - private boolean checkIfUpdateIsPossible(UpdateOperation<K, V> update) { | 279 | + private void doProvisionalUpdate(DatabaseUpdate update, long transactionId) { |
225 | - Versioned<V> existingEntry = get(update.tableName(), update.key()); | 280 | + Map<String, Pair<Long, byte[]>> lockMap = getLockMap(update.tableName()); |
226 | switch (update.type()) { | 281 | switch (update.type()) { |
227 | case PUT: | 282 | case PUT: |
228 | - case REMOVE: | ||
229 | - return true; | ||
230 | case PUT_IF_ABSENT: | 283 | case PUT_IF_ABSENT: |
231 | - return existingEntry == null; | ||
232 | case PUT_IF_VERSION_MATCH: | 284 | case PUT_IF_VERSION_MATCH: |
233 | - return existingEntry != null && existingEntry.version() == update.currentVersion(); | ||
234 | case PUT_IF_VALUE_MATCH: | 285 | case PUT_IF_VALUE_MATCH: |
235 | - return existingEntry != null && checkEquality(existingEntry.value(), update.currentValue()); | 286 | + lockMap.put(update.key(), Pair.of(transactionId, update.value())); |
287 | + break; | ||
288 | + case REMOVE: | ||
236 | case REMOVE_IF_VERSION_MATCH: | 289 | case REMOVE_IF_VERSION_MATCH: |
237 | - return existingEntry == null || existingEntry.version() == update.currentVersion(); | ||
238 | case REMOVE_IF_VALUE_MATCH: | 290 | case REMOVE_IF_VALUE_MATCH: |
239 | - return existingEntry == null || checkEquality(existingEntry.value(), update.currentValue()); | 291 | + lockMap.put(update.key(), null); |
292 | + break; | ||
240 | default: | 293 | default: |
241 | throw new IllegalStateException("Unsupported type: " + update.type()); | 294 | throw new IllegalStateException("Unsupported type: " + update.type()); |
242 | } | 295 | } |
243 | } | 296 | } |
244 | 297 | ||
245 | - private boolean checkEquality(V value1, V value2) { | 298 | + private void commitProvisionalUpdate(DatabaseUpdate update, long transactionId) { |
246 | - if (value1 instanceof byte[]) { | 299 | + String tableName = update.tableName(); |
247 | - return Arrays.equals((byte[]) value1, (byte[]) value2); | 300 | + String key = update.key(); |
301 | + Type type = update.type(); | ||
302 | + Pair<Long, byte[]> provisionalUpdate = getLockMap(tableName).get(key); | ||
303 | + if (Objects.equal(transactionId, provisionalUpdate.getLeft())) { | ||
304 | + getLockMap(tableName).remove(key); | ||
305 | + } else { | ||
306 | + return; | ||
248 | } | 307 | } |
249 | - return value1.equals(value2); | 308 | + |
309 | + switch (type) { | ||
310 | + case PUT: | ||
311 | + case PUT_IF_ABSENT: | ||
312 | + case PUT_IF_VERSION_MATCH: | ||
313 | + case PUT_IF_VALUE_MATCH: | ||
314 | + put(tableName, key, provisionalUpdate.getRight()); | ||
315 | + break; | ||
316 | + case REMOVE: | ||
317 | + case REMOVE_IF_VERSION_MATCH: | ||
318 | + case REMOVE_IF_VALUE_MATCH: | ||
319 | + remove(tableName, key); | ||
320 | + break; | ||
321 | + default: | ||
322 | + break; | ||
323 | + } | ||
324 | + } | ||
325 | + | ||
326 | + private void undoProvisionalUpdate(DatabaseUpdate update, long transactionId) { | ||
327 | + String tableName = update.tableName(); | ||
328 | + String key = update.key(); | ||
329 | + Pair<Long, byte[]> provisionalUpdate = getLockMap(tableName).get(key); | ||
330 | + if (provisionalUpdate == null) { | ||
331 | + return; | ||
332 | + } | ||
333 | + if (Objects.equal(transactionId, provisionalUpdate.getLeft())) { | ||
334 | + getLockMap(tableName).remove(key); | ||
335 | + } | ||
336 | + } | ||
337 | + | ||
338 | + private boolean isLockedByAnotherTransaction(String tableName, String key, long transactionId) { | ||
339 | + Pair<Long, byte[]> update = getLockMap(tableName).get(key); | ||
340 | + return update != null && !Objects.equal(transactionId, update.getLeft()); | ||
341 | + } | ||
342 | + | ||
343 | + private boolean isLockedForUpdates(String tableName, String key) { | ||
344 | + return getLockMap(tableName).containsKey(key); | ||
345 | + } | ||
346 | + | ||
347 | + private boolean areTransactionsInProgress(String tableName) { | ||
348 | + return !getLockMap(tableName).isEmpty(); | ||
250 | } | 349 | } |
251 | } | 350 | } | ... | ... |
... | @@ -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); | ... | ... |
core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransaction.java
0 → 100644
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 |
core/store/dist/src/main/java/org/onosproject/store/consistent/impl/TransactionManager.java
0 → 100644
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 |
-
Please register or login to post a comment