Add sample accessing database service to Foo
Change-Id: I514c57a278dea368448d284eb5bf0d41bb0013e3
Showing
10 changed files
with
305 additions
and
49 deletions
... | @@ -15,6 +15,9 @@ | ... | @@ -15,6 +15,9 @@ |
15 | */ | 15 | */ |
16 | package org.onlab.onos.foo; | 16 | package org.onlab.onos.foo; |
17 | 17 | ||
18 | +import java.nio.ByteBuffer; | ||
19 | +import java.util.concurrent.ScheduledExecutorService; | ||
20 | + | ||
18 | import org.apache.felix.scr.annotations.Activate; | 21 | import org.apache.felix.scr.annotations.Activate; |
19 | import org.apache.felix.scr.annotations.Component; | 22 | import org.apache.felix.scr.annotations.Component; |
20 | import org.apache.felix.scr.annotations.Deactivate; | 23 | import org.apache.felix.scr.annotations.Deactivate; |
... | @@ -33,9 +36,20 @@ import org.onlab.onos.net.device.DeviceService; | ... | @@ -33,9 +36,20 @@ import org.onlab.onos.net.device.DeviceService; |
33 | import org.onlab.onos.net.intent.IntentEvent; | 36 | import org.onlab.onos.net.intent.IntentEvent; |
34 | import org.onlab.onos.net.intent.IntentListener; | 37 | import org.onlab.onos.net.intent.IntentListener; |
35 | import org.onlab.onos.net.intent.IntentService; | 38 | import org.onlab.onos.net.intent.IntentService; |
39 | +import org.onlab.onos.store.service.DatabaseAdminService; | ||
40 | +import org.onlab.onos.store.service.DatabaseException; | ||
41 | +import org.onlab.onos.store.service.DatabaseService; | ||
42 | +import org.onlab.onos.store.service.OptionalResult; | ||
43 | +import org.onlab.onos.store.service.PreconditionFailedException; | ||
44 | +import org.onlab.onos.store.service.ReadRequest; | ||
45 | +import org.onlab.onos.store.service.ReadResult; | ||
46 | +import org.onlab.onos.store.service.WriteRequest; | ||
47 | +import org.onlab.onos.store.service.WriteResult; | ||
36 | import org.slf4j.Logger; | 48 | import org.slf4j.Logger; |
37 | 49 | ||
50 | +import static org.onlab.util.Tools.namedThreads; | ||
38 | import static org.slf4j.LoggerFactory.getLogger; | 51 | import static org.slf4j.LoggerFactory.getLogger; |
52 | +import static java.util.concurrent.Executors.newScheduledThreadPool; | ||
39 | 53 | ||
40 | /** | 54 | /** |
41 | * Playground app component. | 55 | * Playground app component. |
... | @@ -57,22 +71,42 @@ public class FooComponent { | ... | @@ -57,22 +71,42 @@ public class FooComponent { |
57 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 71 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
58 | protected MastershipService mastershipService; | 72 | protected MastershipService mastershipService; |
59 | 73 | ||
74 | + @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY) | ||
75 | + protected DatabaseAdminService dbAdminService; | ||
76 | + | ||
77 | + @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY) | ||
78 | + protected DatabaseService dbService; | ||
79 | + | ||
60 | private final ClusterEventListener clusterListener = new InnerClusterListener(); | 80 | private final ClusterEventListener clusterListener = new InnerClusterListener(); |
61 | private final DeviceListener deviceListener = new InnerDeviceListener(); | 81 | private final DeviceListener deviceListener = new InnerDeviceListener(); |
62 | private final IntentListener intentListener = new InnerIntentListener(); | 82 | private final IntentListener intentListener = new InnerIntentListener(); |
63 | private final MastershipListener mastershipListener = new InnerMastershipListener(); | 83 | private final MastershipListener mastershipListener = new InnerMastershipListener(); |
64 | 84 | ||
85 | + private ScheduledExecutorService executor; | ||
86 | + | ||
65 | @Activate | 87 | @Activate |
66 | public void activate() { | 88 | public void activate() { |
89 | + executor = newScheduledThreadPool(4, namedThreads("foo-executor-%d")); | ||
90 | + | ||
67 | clusterService.addListener(clusterListener); | 91 | clusterService.addListener(clusterListener); |
68 | deviceService.addListener(deviceListener); | 92 | deviceService.addListener(deviceListener); |
69 | intentService.addListener(intentListener); | 93 | intentService.addListener(intentListener); |
70 | mastershipService.addListener(mastershipListener); | 94 | mastershipService.addListener(mastershipListener); |
95 | + | ||
96 | + if (dbService == null || dbAdminService == null) { | ||
97 | + log.info("Couldn't find DB service"); | ||
98 | + } else { | ||
99 | + log.info("Found DB service"); | ||
100 | +// longIncrementor(); | ||
101 | +// executor.scheduleAtFixedRate(new LongIncrementor(), 1, 10, TimeUnit.SECONDS); | ||
102 | +// executor.scheduleAtFixedRate(new LongIncrementor(), 1, 10, TimeUnit.SECONDS); | ||
103 | + } | ||
71 | log.info("Started"); | 104 | log.info("Started"); |
72 | } | 105 | } |
73 | 106 | ||
74 | @Deactivate | 107 | @Deactivate |
75 | public void deactivate() { | 108 | public void deactivate() { |
109 | + executor.shutdown(); | ||
76 | clusterService.removeListener(clusterListener); | 110 | clusterService.removeListener(clusterListener); |
77 | deviceService.removeListener(deviceListener); | 111 | deviceService.removeListener(deviceListener); |
78 | intentService.removeListener(intentListener); | 112 | intentService.removeListener(intentListener); |
... | @@ -122,6 +156,65 @@ public class FooComponent { | ... | @@ -122,6 +156,65 @@ public class FooComponent { |
122 | } | 156 | } |
123 | } | 157 | } |
124 | } | 158 | } |
159 | + | ||
160 | + private void longIncrementor() { | ||
161 | + try { | ||
162 | + final String someTable = "admin"; | ||
163 | + final String someKey = "long"; | ||
164 | + | ||
165 | + dbAdminService.createTable(someTable); | ||
166 | + | ||
167 | + ReadResult read = dbService.read(ReadRequest.get(someTable, someKey)); | ||
168 | + if (!read.valueExists()) { | ||
169 | + ByteBuffer zero = ByteBuffer.allocate(Long.BYTES).putLong(0); | ||
170 | + try { | ||
171 | + dbService.write(WriteRequest | ||
172 | + .putIfAbsent(someTable, | ||
173 | + someKey, | ||
174 | + zero.array())); | ||
175 | + log.info("Wrote initial value"); | ||
176 | + read = dbService.read(ReadRequest.get(someTable, someKey)); | ||
177 | + } catch (PreconditionFailedException e) { | ||
178 | + log.info("Concurrent write detected.", e); | ||
179 | + | ||
180 | + // concurrent write detected, read and fall through | ||
181 | + read = dbService.read(ReadRequest.get(someTable, someKey)); | ||
182 | + if (!read.valueExists()) { | ||
183 | + log.error("Shouldn't reach here"); | ||
184 | + } | ||
185 | + } | ||
186 | + } | ||
187 | + int retry = 5; | ||
188 | + do { | ||
189 | + ByteBuffer prev = ByteBuffer.wrap(read.value().value()); | ||
190 | + long next = prev.getLong() + 1; | ||
191 | + byte[] newValue = ByteBuffer.allocate(Long.BYTES).putLong(next).array(); | ||
192 | + OptionalResult<WriteResult, DatabaseException> result | ||
193 | + = dbService.writeNothrow(WriteRequest | ||
194 | + .putIfVersionMatches(someTable, | ||
195 | + someKey, | ||
196 | + newValue, | ||
197 | + read.value().version())); | ||
198 | + if (result.hasValidResult()) { | ||
199 | + log.info("Write success {} -> {}", result.get().previousValue(), next); | ||
200 | + break; | ||
201 | + } else { | ||
202 | + log.info("Write failed trying to write{}", next); | ||
203 | + } | ||
204 | + } while(retry-- > 0); | ||
205 | + } catch (Exception e) { | ||
206 | + log.error("Exception thrown", e); | ||
207 | + } | ||
208 | + } | ||
209 | + | ||
210 | + private final class LongIncrementor implements Runnable { | ||
211 | + | ||
212 | + @Override | ||
213 | + public void run() { | ||
214 | + longIncrementor(); | ||
215 | + } | ||
216 | + | ||
217 | + } | ||
125 | } | 218 | } |
126 | 219 | ||
127 | 220 | ... | ... |
... | @@ -24,13 +24,24 @@ public interface DatabaseService { | ... | @@ -24,13 +24,24 @@ public interface DatabaseService { |
24 | */ | 24 | */ |
25 | List<OptionalResult<ReadResult, DatabaseException>> batchRead(List<ReadRequest> batch); | 25 | List<OptionalResult<ReadResult, DatabaseException>> batchRead(List<ReadRequest> batch); |
26 | 26 | ||
27 | + // FIXME Give me a better name | ||
27 | /** | 28 | /** |
28 | * Performs a write operation on the database. | 29 | * Performs a write operation on the database. |
29 | * @param request write request | 30 | * @param request write request |
30 | * @return write result. | 31 | * @return write result. |
31 | * @throws DatabaseException if there is failure in execution write. | 32 | * @throws DatabaseException if there is failure in execution write. |
32 | */ | 33 | */ |
33 | - WriteResult write(WriteRequest request); | 34 | + OptionalResult<WriteResult, DatabaseException> writeNothrow(WriteRequest request); |
35 | + | ||
36 | + /** | ||
37 | + * Performs a write operation on the database. | ||
38 | + * @param request write request | ||
39 | + * @return write result. | ||
40 | + * @throws OptimisticLockException FIXME define conditional failure | ||
41 | + * @throws PreconditionFailedException FIXME define conditional failure | ||
42 | + * @throws DatabaseException if there is failure in execution write. | ||
43 | + */ | ||
44 | + WriteResult write(WriteRequest request)/* throws OptimisticLockException, PreconditionFailedException*/; | ||
34 | 45 | ||
35 | /** | 46 | /** |
36 | * Performs a batch write operation on the database. | 47 | * Performs a batch write operation on the database. | ... | ... |
1 | package org.onlab.onos.store.service; | 1 | package org.onlab.onos.store.service; |
2 | 2 | ||
3 | - | ||
4 | /** | 3 | /** |
5 | * Exception that indicates a precondition failure. | 4 | * Exception that indicates a precondition failure. |
6 | * Scenarios that can cause this exception: | 5 | * Scenarios that can cause this exception: | ... | ... |
... | @@ -35,6 +35,15 @@ public class ReadResult { | ... | @@ -35,6 +35,15 @@ public class ReadResult { |
35 | } | 35 | } |
36 | 36 | ||
37 | /** | 37 | /** |
38 | + * Returns true if database table contained value for the key. | ||
39 | + * | ||
40 | + * @return true if database table contained value for the key | ||
41 | + */ | ||
42 | + public boolean valueExists() { | ||
43 | + return value != null; | ||
44 | + } | ||
45 | + | ||
46 | + /** | ||
38 | * Returns value associated with the key. | 47 | * Returns value associated with the key. |
39 | * @return non-null value if the table contains one, null otherwise. | 48 | * @return non-null value if the table contains one, null otherwise. |
40 | */ | 49 | */ | ... | ... |
1 | package org.onlab.onos.store.service; | 1 | package org.onlab.onos.store.service; |
2 | 2 | ||
3 | import static com.google.common.base.Preconditions.checkArgument; | 3 | import static com.google.common.base.Preconditions.checkArgument; |
4 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
5 | +import static org.onlab.onos.store.service.WriteRequest.Type.*; | ||
4 | 6 | ||
5 | import java.util.Objects; | 7 | import java.util.Objects; |
6 | 8 | ||
... | @@ -11,8 +13,13 @@ import com.google.common.base.MoreObjects; | ... | @@ -11,8 +13,13 @@ import com.google.common.base.MoreObjects; |
11 | */ | 13 | */ |
12 | public class WriteRequest { | 14 | public class WriteRequest { |
13 | 15 | ||
16 | + public static final int ANY_VERSION = -1; | ||
17 | + | ||
14 | private final String tableName; | 18 | private final String tableName; |
15 | private final String key; | 19 | private final String key; |
20 | + | ||
21 | + private final Type type; | ||
22 | + | ||
16 | private final byte[] newValue; | 23 | private final byte[] newValue; |
17 | private final long previousVersion; | 24 | private final long previousVersion; |
18 | private final byte[] oldValue; | 25 | private final byte[] oldValue; |
... | @@ -23,22 +30,22 @@ public class WriteRequest { | ... | @@ -23,22 +30,22 @@ public class WriteRequest { |
23 | * | 30 | * |
24 | * @param tableName name of the table | 31 | * @param tableName name of the table |
25 | * @param key key in the table | 32 | * @param key key in the table |
26 | - * @param newValue value to write | 33 | + * @param newValue value to write, must not be null |
27 | * @return WriteRequest | 34 | * @return WriteRequest |
28 | */ | 35 | */ |
29 | public static WriteRequest put(String tableName, String key, | 36 | public static WriteRequest put(String tableName, String key, |
30 | byte[] newValue) { | 37 | byte[] newValue) { |
31 | - return new WriteRequest(tableName, key, newValue, -1, null); | 38 | + return new WriteRequest(PUT, tableName, key, |
39 | + checkNotNull(newValue), ANY_VERSION, null); | ||
32 | } | 40 | } |
33 | 41 | ||
34 | - // FIXME: Is there a special version value to realize putIfAbsent? | ||
35 | /** | 42 | /** |
36 | * Creates a write request, which will | 43 | * Creates a write request, which will |
37 | * put the specified value to the table if the previous version matches. | 44 | * put the specified value to the table if the previous version matches. |
38 | * | 45 | * |
39 | * @param tableName name of the table | 46 | * @param tableName name of the table |
40 | * @param key key in the table | 47 | * @param key key in the table |
41 | - * @param newValue value to write | 48 | + * @param newValue value to write, must not be null |
42 | * @param previousVersion previous version expected | 49 | * @param previousVersion previous version expected |
43 | * @return WriteRequest | 50 | * @return WriteRequest |
44 | */ | 51 | */ |
... | @@ -46,37 +53,107 @@ public class WriteRequest { | ... | @@ -46,37 +53,107 @@ public class WriteRequest { |
46 | byte[] newValue, | 53 | byte[] newValue, |
47 | long previousVersion) { | 54 | long previousVersion) { |
48 | checkArgument(previousVersion >= 0); | 55 | checkArgument(previousVersion >= 0); |
49 | - return new WriteRequest(tableName, key, newValue, previousVersion, null); | 56 | + return new WriteRequest(PUT_IF_VERSION, tableName, key, |
57 | + checkNotNull(newValue), previousVersion, null); | ||
50 | } | 58 | } |
51 | 59 | ||
52 | - // FIXME: What is the behavior of oldValue=null? putIfAbsent? | ||
53 | /** | 60 | /** |
54 | * Creates a write request, which will | 61 | * Creates a write request, which will |
55 | * put the specified value to the table if the previous value matches. | 62 | * put the specified value to the table if the previous value matches. |
56 | * | 63 | * |
57 | * @param tableName name of the table | 64 | * @param tableName name of the table |
58 | * @param key key in the table | 65 | * @param key key in the table |
59 | - * @param newValue value to write | 66 | + * @param newValue value to write, must not be null |
60 | - * @param oldValue previous value expected | 67 | + * @param oldValue previous value expected, must not be null |
61 | * @return WriteRequest | 68 | * @return WriteRequest |
62 | */ | 69 | */ |
63 | public static WriteRequest putIfValueMatches(String tableName, String key, | 70 | public static WriteRequest putIfValueMatches(String tableName, String key, |
64 | byte[] newValue, | 71 | byte[] newValue, |
65 | byte[] oldValue) { | 72 | byte[] oldValue) { |
66 | - return new WriteRequest(tableName, key, newValue, -1, oldValue); | 73 | + return new WriteRequest(PUT_IF_VALUE, tableName, key, |
74 | + checkNotNull(newValue), ANY_VERSION, | ||
75 | + checkNotNull(oldValue)); | ||
76 | + } | ||
77 | + | ||
78 | + /** | ||
79 | + * Creates a write request, which will | ||
80 | + * put the specified value to the table if the previous value does not exist. | ||
81 | + * | ||
82 | + * @param tableName name of the table | ||
83 | + * @param key key in the table | ||
84 | + * @param newValue value to write, must not be null | ||
85 | + * @return WriteRequest | ||
86 | + */ | ||
87 | + public static WriteRequest putIfAbsent(String tableName, String key, | ||
88 | + byte[] newValue) { | ||
89 | + return new WriteRequest(PUT_IF_ABSENT, tableName, key, | ||
90 | + checkNotNull(newValue), ANY_VERSION, null); | ||
67 | } | 91 | } |
68 | 92 | ||
69 | - // FIXME: How do we remove value? newValue=null? | 93 | + /** |
94 | + * Creates a write request, which will | ||
95 | + * remove the specified entry from the table regardless of the previous value. | ||
96 | + * | ||
97 | + * @param tableName name of the table | ||
98 | + * @param key key in the table | ||
99 | + * @return WriteRequest | ||
100 | + */ | ||
101 | + public static WriteRequest remove(String tableName, String key) { | ||
102 | + return new WriteRequest(REMOVE, tableName, key, | ||
103 | + null, ANY_VERSION, null); | ||
104 | + } | ||
105 | + | ||
106 | + /** | ||
107 | + * Creates a write request, which will | ||
108 | + * remove the specified entry from the table if the previous version matches. | ||
109 | + * | ||
110 | + * @param tableName name of the table | ||
111 | + * @param key key in the table | ||
112 | + * @param previousVersion previous version expected | ||
113 | + * @return WriteRequest | ||
114 | + */ | ||
115 | + public static WriteRequest remove(String tableName, String key, | ||
116 | + long previousVersion) { | ||
117 | + return new WriteRequest(REMOVE_IF_VALUE, tableName, key, | ||
118 | + null, previousVersion, null); | ||
119 | + } | ||
120 | + | ||
121 | + /** | ||
122 | + * Creates a write request, which will | ||
123 | + * remove the specified entry from the table if the previous value matches. | ||
124 | + * | ||
125 | + * @param tableName name of the table | ||
126 | + * @param key key in the table | ||
127 | + * @param oldValue previous value expected, must not be null | ||
128 | + * @return WriteRequest | ||
129 | + */ | ||
130 | + public static WriteRequest remove(String tableName, String key, | ||
131 | + byte[] oldValue) { | ||
132 | + return new WriteRequest(Type.REMOVE_IF_VALUE, tableName, key, | ||
133 | + null, ANY_VERSION, checkNotNull(oldValue)); | ||
134 | + } | ||
135 | + | ||
136 | + public enum Type { | ||
137 | + PUT, | ||
138 | + PUT_IF_VERSION, | ||
139 | + PUT_IF_VALUE, | ||
140 | + PUT_IF_ABSENT, | ||
141 | + REMOVE, | ||
142 | + REMOVE_IF_VERSION, | ||
143 | + REMOVE_IF_VALUE, | ||
144 | + } | ||
70 | 145 | ||
71 | // hidden constructor | 146 | // hidden constructor |
72 | - protected WriteRequest(String tableName, String key, byte[] newValue, long previousVersion, byte[] oldValue) { | 147 | + protected WriteRequest(Type type, String tableName, String key, |
148 | + byte[] newValue, | ||
149 | + long previousVersion, byte[] oldValue) { | ||
73 | 150 | ||
74 | - checkArgument(tableName != null); | 151 | + checkNotNull(tableName); |
75 | - checkArgument(key != null); | 152 | + checkNotNull(key); |
76 | - checkArgument(newValue != null); | ||
77 | 153 | ||
78 | this.tableName = tableName; | 154 | this.tableName = tableName; |
79 | this.key = key; | 155 | this.key = key; |
156 | + this.type = type; | ||
80 | this.newValue = newValue; | 157 | this.newValue = newValue; |
81 | this.previousVersion = previousVersion; | 158 | this.previousVersion = previousVersion; |
82 | this.oldValue = oldValue; | 159 | this.oldValue = oldValue; |
... | @@ -90,6 +167,10 @@ public class WriteRequest { | ... | @@ -90,6 +167,10 @@ public class WriteRequest { |
90 | return key; | 167 | return key; |
91 | } | 168 | } |
92 | 169 | ||
170 | + public WriteRequest.Type type() { | ||
171 | + return type; | ||
172 | + } | ||
173 | + | ||
93 | public byte[] newValue() { | 174 | public byte[] newValue() { |
94 | return newValue; | 175 | return newValue; |
95 | } | 176 | } |
... | @@ -105,6 +186,7 @@ public class WriteRequest { | ... | @@ -105,6 +186,7 @@ public class WriteRequest { |
105 | @Override | 186 | @Override |
106 | public String toString() { | 187 | public String toString() { |
107 | return MoreObjects.toStringHelper(getClass()) | 188 | return MoreObjects.toStringHelper(getClass()) |
189 | + .add("type", type) | ||
108 | .add("tableName", tableName) | 190 | .add("tableName", tableName) |
109 | .add("key", key) | 191 | .add("key", key) |
110 | .add("newValue", newValue) | 192 | .add("newValue", newValue) |
... | @@ -113,9 +195,10 @@ public class WriteRequest { | ... | @@ -113,9 +195,10 @@ public class WriteRequest { |
113 | .toString(); | 195 | .toString(); |
114 | } | 196 | } |
115 | 197 | ||
198 | + // TODO: revisit hashCode, equals condition | ||
116 | @Override | 199 | @Override |
117 | public int hashCode() { | 200 | public int hashCode() { |
118 | - return Objects.hash(key, tableName, previousVersion); | 201 | + return Objects.hash(type, key, tableName, previousVersion); |
119 | } | 202 | } |
120 | 203 | ||
121 | @Override | 204 | @Override |
... | @@ -130,7 +213,8 @@ public class WriteRequest { | ... | @@ -130,7 +213,8 @@ public class WriteRequest { |
130 | return false; | 213 | return false; |
131 | } | 214 | } |
132 | WriteRequest other = (WriteRequest) obj; | 215 | WriteRequest other = (WriteRequest) obj; |
133 | - return Objects.equals(this.key, other.key) && | 216 | + return Objects.equals(this.type, other.type) && |
217 | + Objects.equals(this.key, other.key) && | ||
134 | Objects.equals(this.tableName, other.tableName) && | 218 | Objects.equals(this.tableName, other.tableName) && |
135 | Objects.equals(this.previousVersion, other.previousVersion); | 219 | Objects.equals(this.previousVersion, other.previousVersion); |
136 | } | 220 | } | ... | ... |
... | @@ -105,6 +105,7 @@ public class ClusterMessagingProtocol | ... | @@ -105,6 +105,7 @@ public class ClusterMessagingProtocol |
105 | private static final KryoNamespace DATABASE = KryoNamespace.newBuilder() | 105 | private static final KryoNamespace DATABASE = KryoNamespace.newBuilder() |
106 | .register(ReadRequest.class) | 106 | .register(ReadRequest.class) |
107 | .register(WriteRequest.class) | 107 | .register(WriteRequest.class) |
108 | + .register(WriteRequest.Type.class) | ||
108 | .register(InternalReadResult.class) | 109 | .register(InternalReadResult.class) |
109 | .register(InternalWriteResult.class) | 110 | .register(InternalWriteResult.class) |
110 | .register(InternalReadResult.Status.class) | 111 | .register(InternalReadResult.Status.class) |
... | @@ -135,6 +136,7 @@ public class ClusterMessagingProtocol | ... | @@ -135,6 +136,7 @@ public class ClusterMessagingProtocol |
135 | byte[].class) | 136 | byte[].class) |
136 | .build(); | 137 | .build(); |
137 | 138 | ||
139 | + // serializer used for CopyCat Protocol | ||
138 | public static final KryoSerializer SERIALIZER = new KryoSerializer() { | 140 | public static final KryoSerializer SERIALIZER = new KryoSerializer() { |
139 | @Override | 141 | @Override |
140 | protected void setupKryoPool() { | 142 | protected void setupKryoPool() { | ... | ... |
... | @@ -2,6 +2,7 @@ package org.onlab.onos.store.service.impl; | ... | @@ -2,6 +2,7 @@ package org.onlab.onos.store.service.impl; |
2 | 2 | ||
3 | import static org.slf4j.LoggerFactory.getLogger; | 3 | import static org.slf4j.LoggerFactory.getLogger; |
4 | 4 | ||
5 | +import java.io.File; | ||
5 | import java.util.ArrayList; | 6 | import java.util.ArrayList; |
6 | import java.util.Arrays; | 7 | import java.util.Arrays; |
7 | import java.util.List; | 8 | import java.util.List; |
... | @@ -57,7 +58,7 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { | ... | @@ -57,7 +58,7 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { |
57 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 58 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
58 | protected DatabaseProtocolService copycatMessagingProtocol; | 59 | protected DatabaseProtocolService copycatMessagingProtocol; |
59 | 60 | ||
60 | - public static final String LOG_FILE_PREFIX = "onos-copy-cat-log"; | 61 | + public static final String LOG_FILE_PREFIX = "/tmp/onos-copy-cat-log"; |
61 | 62 | ||
62 | private Copycat copycat; | 63 | private Copycat copycat; |
63 | private DatabaseClient client; | 64 | private DatabaseClient client; |
... | @@ -126,9 +127,11 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { | ... | @@ -126,9 +127,11 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { |
126 | 127 | ||
127 | 128 | ||
128 | StateMachine stateMachine = new DatabaseStateMachine(); | 129 | StateMachine stateMachine = new DatabaseStateMachine(); |
129 | - // FIXME resolve Chronicle + OSGi issue | 130 | + // Chronicle + OSGi issue |
130 | //Log consensusLog = new ChronicleLog(LOG_FILE_PREFIX + "_" + thisNode.id()); | 131 | //Log consensusLog = new ChronicleLog(LOG_FILE_PREFIX + "_" + thisNode.id()); |
131 | - Log consensusLog = new KryoRegisteredInMemoryLog(); | 132 | + //Log consensusLog = new KryoRegisteredInMemoryLog(); |
133 | + Log consensusLog = new MapDBLog(new File(LOG_FILE_PREFIX + localNode.id()), | ||
134 | + ClusterMessagingProtocol.SERIALIZER); | ||
132 | 135 | ||
133 | copycat = new Copycat(stateMachine, consensusLog, cluster, copycatMessagingProtocol); | 136 | copycat = new Copycat(stateMachine, consensusLog, cluster, copycatMessagingProtocol); |
134 | copycat.start(); | 137 | copycat.start(); |
... | @@ -187,8 +190,14 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { | ... | @@ -187,8 +190,14 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { |
187 | } | 190 | } |
188 | 191 | ||
189 | @Override | 192 | @Override |
190 | - public WriteResult write(WriteRequest request) { | 193 | + public OptionalResult<WriteResult, DatabaseException> writeNothrow(WriteRequest request) { |
191 | - return batchWrite(Arrays.asList(request)).get(0).get(); | 194 | + return batchWrite(Arrays.asList(request)).get(0); |
195 | + } | ||
196 | + | ||
197 | + @Override | ||
198 | + public WriteResult write(WriteRequest request) | ||
199 | + throws OptimisticLockException, PreconditionFailedException { | ||
200 | + return writeNothrow(request).get(); | ||
192 | } | 201 | } |
193 | 202 | ||
194 | @Override | 203 | @Override |
... | @@ -199,13 +208,13 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { | ... | @@ -199,13 +208,13 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { |
199 | if (internalWriteResult.status() == InternalWriteResult.Status.NO_SUCH_TABLE) { | 208 | if (internalWriteResult.status() == InternalWriteResult.Status.NO_SUCH_TABLE) { |
200 | writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( | 209 | writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( |
201 | new NoSuchTableException())); | 210 | new NoSuchTableException())); |
202 | - } else if (internalWriteResult.status() == InternalWriteResult.Status.OPTIMISTIC_LOCK_FAILURE) { | 211 | + } else if (internalWriteResult.status() == InternalWriteResult.Status.PREVIOUS_VERSION_MISMATCH) { |
203 | writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( | 212 | writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( |
204 | new OptimisticLockException())); | 213 | new OptimisticLockException())); |
205 | } else if (internalWriteResult.status() == InternalWriteResult.Status.PREVIOUS_VALUE_MISMATCH) { | 214 | } else if (internalWriteResult.status() == InternalWriteResult.Status.PREVIOUS_VALUE_MISMATCH) { |
206 | // TODO: throw a different exception? | 215 | // TODO: throw a different exception? |
207 | writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( | 216 | writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( |
208 | - new PreconditionFailedException())); | 217 | + new OptimisticLockException())); |
209 | } else if (internalWriteResult.status() == InternalWriteResult.Status.ABORTED) { | 218 | } else if (internalWriteResult.status() == InternalWriteResult.Status.ABORTED) { |
210 | writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( | 219 | writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( |
211 | new WriteAborted())); | 220 | new WriteAborted())); | ... | ... |
... | @@ -3,8 +3,10 @@ package org.onlab.onos.store.service.impl; | ... | @@ -3,8 +3,10 @@ package org.onlab.onos.store.service.impl; |
3 | import static org.slf4j.LoggerFactory.getLogger; | 3 | import static org.slf4j.LoggerFactory.getLogger; |
4 | 4 | ||
5 | import java.util.ArrayList; | 5 | import java.util.ArrayList; |
6 | +import java.util.Arrays; | ||
6 | import java.util.List; | 7 | import java.util.List; |
7 | import java.util.Map; | 8 | import java.util.Map; |
9 | + | ||
8 | import net.kuujo.copycat.Command; | 10 | import net.kuujo.copycat.Command; |
9 | import net.kuujo.copycat.Query; | 11 | import net.kuujo.copycat.Query; |
10 | import net.kuujo.copycat.StateMachine; | 12 | import net.kuujo.copycat.StateMachine; |
... | @@ -15,6 +17,7 @@ import org.onlab.onos.store.service.ReadResult; | ... | @@ -15,6 +17,7 @@ import org.onlab.onos.store.service.ReadResult; |
15 | import org.onlab.onos.store.service.VersionedValue; | 17 | import org.onlab.onos.store.service.VersionedValue; |
16 | import org.onlab.onos.store.service.WriteRequest; | 18 | import org.onlab.onos.store.service.WriteRequest; |
17 | import org.onlab.onos.store.service.WriteResult; | 19 | import org.onlab.onos.store.service.WriteResult; |
20 | +import org.onlab.onos.store.service.impl.InternalWriteResult.Status; | ||
18 | import org.onlab.util.KryoNamespace; | 21 | import org.onlab.util.KryoNamespace; |
19 | import org.slf4j.Logger; | 22 | import org.slf4j.Logger; |
20 | 23 | ||
... | @@ -32,6 +35,7 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -32,6 +35,7 @@ public class DatabaseStateMachine implements StateMachine { |
32 | 35 | ||
33 | private final Logger log = getLogger(getClass()); | 36 | private final Logger log = getLogger(getClass()); |
34 | 37 | ||
38 | + // serializer used for snapshot | ||
35 | public static final KryoSerializer SERIALIZER = new KryoSerializer() { | 39 | public static final KryoSerializer SERIALIZER = new KryoSerializer() { |
36 | @Override | 40 | @Override |
37 | protected void setupKryoPool() { | 41 | protected void setupKryoPool() { |
... | @@ -87,6 +91,37 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -87,6 +91,37 @@ public class DatabaseStateMachine implements StateMachine { |
87 | return results; | 91 | return results; |
88 | } | 92 | } |
89 | 93 | ||
94 | + InternalWriteResult.Status checkIfApplicable(WriteRequest request, | ||
95 | + VersionedValue value) { | ||
96 | + | ||
97 | + switch (request.type()) { | ||
98 | + case PUT: | ||
99 | + return InternalWriteResult.Status.OK; | ||
100 | + | ||
101 | + case PUT_IF_ABSENT: | ||
102 | + if (value == null) { | ||
103 | + return InternalWriteResult.Status.OK; | ||
104 | + } | ||
105 | + return InternalWriteResult.Status.PREVIOUS_VALUE_MISMATCH; | ||
106 | + case PUT_IF_VALUE: | ||
107 | + case REMOVE_IF_VALUE: | ||
108 | + if (value != null && Arrays.equals(value.value(), request.oldValue())) { | ||
109 | + return InternalWriteResult.Status.OK; | ||
110 | + } | ||
111 | + return InternalWriteResult.Status.PREVIOUS_VALUE_MISMATCH; | ||
112 | + case PUT_IF_VERSION: | ||
113 | + case REMOVE_IF_VERSION: | ||
114 | + if (value != null && request.previousVersion() == value.version()) { | ||
115 | + return InternalWriteResult.Status.OK; | ||
116 | + } | ||
117 | + return InternalWriteResult.Status.PREVIOUS_VERSION_MISMATCH; | ||
118 | + case REMOVE: | ||
119 | + return InternalWriteResult.Status.OK; | ||
120 | + } | ||
121 | + log.error("Should never reach here {}", request); | ||
122 | + return InternalWriteResult.Status.ABORTED; | ||
123 | + } | ||
124 | + | ||
90 | @Command | 125 | @Command |
91 | public List<InternalWriteResult> write(List<WriteRequest> requests) { | 126 | public List<InternalWriteResult> write(List<WriteRequest> requests) { |
92 | 127 | ||
... | @@ -100,25 +135,12 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -100,25 +135,12 @@ public class DatabaseStateMachine implements StateMachine { |
100 | abort = true; | 135 | abort = true; |
101 | continue; | 136 | continue; |
102 | } | 137 | } |
103 | - VersionedValue value = table.get(request.key()); | 138 | + final VersionedValue value = table.get(request.key()); |
104 | - if (value == null) { | 139 | + Status result = checkIfApplicable(request, value); |
105 | - if (request.oldValue() != null) { | 140 | + validationResults.add(result); |
106 | - validationResults.add(InternalWriteResult.Status.PREVIOUS_VALUE_MISMATCH); | 141 | + if (result != Status.OK) { |
107 | - abort = true; | ||
108 | - continue; | ||
109 | - } else if (request.previousVersion() >= 0) { | ||
110 | - validationResults.add(InternalWriteResult.Status.OPTIMISTIC_LOCK_FAILURE); | ||
111 | - abort = true; | ||
112 | - continue; | ||
113 | - } | ||
114 | - } | ||
115 | - if (request.previousVersion() >= 0 && value.version() != request.previousVersion()) { | ||
116 | - validationResults.add(InternalWriteResult.Status.OPTIMISTIC_LOCK_FAILURE); | ||
117 | abort = true; | 142 | abort = true; |
118 | - continue; | ||
119 | } | 143 | } |
120 | - | ||
121 | - validationResults.add(InternalWriteResult.Status.OK); | ||
122 | } | 144 | } |
123 | 145 | ||
124 | List<InternalWriteResult> results = new ArrayList<>(requests.size()); | 146 | List<InternalWriteResult> results = new ArrayList<>(requests.size()); |
... | @@ -126,6 +148,7 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -126,6 +148,7 @@ public class DatabaseStateMachine implements StateMachine { |
126 | if (abort) { | 148 | if (abort) { |
127 | for (InternalWriteResult.Status validationResult : validationResults) { | 149 | for (InternalWriteResult.Status validationResult : validationResults) { |
128 | if (validationResult == InternalWriteResult.Status.OK) { | 150 | if (validationResult == InternalWriteResult.Status.OK) { |
151 | + // aborted due to applicability check failure on other request | ||
129 | results.add(new InternalWriteResult(InternalWriteResult.Status.ABORTED, null)); | 152 | results.add(new InternalWriteResult(InternalWriteResult.Status.ABORTED, null)); |
130 | } else { | 153 | } else { |
131 | results.add(new InternalWriteResult(validationResult, null)); | 154 | results.add(new InternalWriteResult(validationResult, null)); |
... | @@ -141,12 +164,31 @@ public class DatabaseStateMachine implements StateMachine { | ... | @@ -141,12 +164,31 @@ public class DatabaseStateMachine implements StateMachine { |
141 | // synchronization scope is wrong. | 164 | // synchronization scope is wrong. |
142 | // Whole function including applicability check needs to be protected. | 165 | // Whole function including applicability check needs to be protected. |
143 | // Confirm copycat's thread safety requirement for StateMachine | 166 | // Confirm copycat's thread safety requirement for StateMachine |
167 | + // TODO: If we need isolation, we need to block reads also | ||
144 | synchronized (table) { | 168 | synchronized (table) { |
145 | - VersionedValue previousValue = | 169 | + switch (request.type()) { |
146 | - table.put(request.key(), new VersionedValue(request.newValue(), state.nextVersion())); | 170 | + case PUT: |
147 | - results.add(new InternalWriteResult( | 171 | + case PUT_IF_ABSENT: |
148 | - InternalWriteResult.Status.OK, | 172 | + case PUT_IF_VALUE: |
149 | - new WriteResult(request.tableName(), request.key(), previousValue))); | 173 | + case PUT_IF_VERSION: |
174 | + VersionedValue newValue = new VersionedValue(request.newValue(), state.nextVersion()); | ||
175 | + VersionedValue previousValue = table.put(request.key(), newValue); | ||
176 | + WriteResult putResult = new WriteResult(request.tableName(), request.key(), previousValue); | ||
177 | + results.add(InternalWriteResult.ok(putResult)); | ||
178 | + break; | ||
179 | + | ||
180 | + case REMOVE: | ||
181 | + case REMOVE_IF_VALUE: | ||
182 | + case REMOVE_IF_VERSION: | ||
183 | + VersionedValue removedValue = table.remove(request.key()); | ||
184 | + WriteResult removeResult = new WriteResult(request.tableName(), request.key(), removedValue); | ||
185 | + results.add(InternalWriteResult.ok(removeResult)); | ||
186 | + break; | ||
187 | + | ||
188 | + default: | ||
189 | + log.error("Invalid WriteRequest type {}", request.type()); | ||
190 | + break; | ||
191 | + } | ||
150 | } | 192 | } |
151 | } | 193 | } |
152 | return results; | 194 | return results; | ... | ... |
1 | package org.onlab.onos.store.service.impl; | 1 | package org.onlab.onos.store.service.impl; |
2 | 2 | ||
3 | +import java.io.Serializable; | ||
4 | + | ||
3 | import org.onlab.onos.store.service.ReadResult; | 5 | import org.onlab.onos.store.service.ReadResult; |
4 | 6 | ||
5 | /** | 7 | /** |
6 | * Result of a read operation executed on the DatabaseStateMachine. | 8 | * Result of a read operation executed on the DatabaseStateMachine. |
7 | */ | 9 | */ |
8 | -public class InternalReadResult { | 10 | +@SuppressWarnings("serial") |
11 | +public class InternalReadResult implements Serializable { | ||
9 | 12 | ||
10 | public enum Status { | 13 | public enum Status { |
11 | OK, | 14 | OK, | ... | ... |
... | @@ -11,13 +11,17 @@ public class InternalWriteResult { | ... | @@ -11,13 +11,17 @@ public class InternalWriteResult { |
11 | OK, | 11 | OK, |
12 | ABORTED, | 12 | ABORTED, |
13 | NO_SUCH_TABLE, | 13 | NO_SUCH_TABLE, |
14 | - OPTIMISTIC_LOCK_FAILURE, | 14 | + PREVIOUS_VERSION_MISMATCH, |
15 | PREVIOUS_VALUE_MISMATCH | 15 | PREVIOUS_VALUE_MISMATCH |
16 | } | 16 | } |
17 | 17 | ||
18 | private final Status status; | 18 | private final Status status; |
19 | private final WriteResult result; | 19 | private final WriteResult result; |
20 | 20 | ||
21 | + public static InternalWriteResult ok(WriteResult result) { | ||
22 | + return new InternalWriteResult(Status.OK, result); | ||
23 | + } | ||
24 | + | ||
21 | public InternalWriteResult(Status status, WriteResult result) { | 25 | public InternalWriteResult(Status status, WriteResult result) { |
22 | this.status = status; | 26 | this.status = status; |
23 | this.result = result; | 27 | this.result = result; | ... | ... |
-
Please register or login to post a comment