DatabaseService that uses Copycat Raft to provide a strongly consistent and durable database.
Showing
25 changed files
with
1439 additions
and
6 deletions
... | @@ -44,6 +44,24 @@ | ... | @@ -44,6 +44,24 @@ |
44 | <version>${project.version}</version> | 44 | <version>${project.version}</version> |
45 | </dependency> | 45 | </dependency> |
46 | 46 | ||
47 | + <dependency> | ||
48 | + <groupId>net.kuujo.copycat</groupId> | ||
49 | + <artifactId>copycat</artifactId> | ||
50 | + <version>0.4.0-SNAPSHOT</version> | ||
51 | + </dependency> | ||
52 | + | ||
53 | + <dependency> | ||
54 | + <groupId>net.kuujo.copycat</groupId> | ||
55 | + <artifactId>copycat-chronicle</artifactId> | ||
56 | + <version>0.4.0-SNAPSHOT</version> | ||
57 | + </dependency> | ||
58 | + | ||
59 | + <dependency> | ||
60 | + <groupId>net.kuujo.copycat</groupId> | ||
61 | + <artifactId>copycat-tcp</artifactId> | ||
62 | + <version>0.4.0-SNAPSHOT</version> | ||
63 | + </dependency> | ||
64 | + | ||
47 | <dependency> | 65 | <dependency> |
48 | <groupId>com.fasterxml.jackson.core</groupId> | 66 | <groupId>com.fasterxml.jackson.core</groupId> |
49 | <artifactId>jackson-databind</artifactId> | 67 | <artifactId>jackson-databind</artifactId> | ... | ... |
1 | +package org.onlab.onos.store.service; | ||
2 | + | ||
3 | +import java.util.List; | ||
4 | + | ||
5 | +/** | ||
6 | + * Service for running administrative tasks on a Database. | ||
7 | + */ | ||
8 | +public interface DatabaseAdminService { | ||
9 | + | ||
10 | + /** | ||
11 | + * Creates a new table. | ||
12 | + * Table creation is idempotent. Attempting to create a table | ||
13 | + * that already exists will be a noop. | ||
14 | + * @param name table name. | ||
15 | + * @return true if the table was created by this call, false otherwise. | ||
16 | + */ | ||
17 | + public boolean createTable(String name); | ||
18 | + | ||
19 | + /** | ||
20 | + * Lists all the tables in the database. | ||
21 | + * @return list of table names. | ||
22 | + */ | ||
23 | + public List<String> listTables(); | ||
24 | + | ||
25 | + /** | ||
26 | + * Deletes a table from the database. | ||
27 | + * @param name name of the table to delete. | ||
28 | + */ | ||
29 | + public void dropTable(String name); | ||
30 | + | ||
31 | + /** | ||
32 | + * Deletes all tables from the database. | ||
33 | + */ | ||
34 | + public void dropAllTables(); | ||
35 | +} |
1 | +package org.onlab.onos.store.service; | ||
2 | + | ||
3 | +/** | ||
4 | + * Base exception type for database failures. | ||
5 | + */ | ||
6 | +@SuppressWarnings("serial") | ||
7 | +public class DatabaseException extends RuntimeException { | ||
8 | + public DatabaseException(String message, Throwable t) { | ||
9 | + super(message, t); | ||
10 | + } | ||
11 | + | ||
12 | + public DatabaseException(String message) { | ||
13 | + super(message); | ||
14 | + } | ||
15 | + | ||
16 | + public DatabaseException(Throwable t) { | ||
17 | + super(t); | ||
18 | + } | ||
19 | + | ||
20 | + public DatabaseException() { | ||
21 | + }; | ||
22 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +package org.onlab.onos.store.service; | ||
2 | + | ||
3 | +import java.util.List; | ||
4 | + | ||
5 | +public interface DatabaseService { | ||
6 | + | ||
7 | + /** | ||
8 | + * Performs a read on the database. | ||
9 | + * @param request read request. | ||
10 | + * @return ReadResult | ||
11 | + * @throws DatabaseException | ||
12 | + */ | ||
13 | + ReadResult read(ReadRequest request); | ||
14 | + | ||
15 | + /** | ||
16 | + * Performs a batch read operation on the database. | ||
17 | + * The main advantage of batch read operation is parallelization. | ||
18 | + * @param batch batch of read requests to execute. | ||
19 | + * @return | ||
20 | + */ | ||
21 | + List<OptionalResult<ReadResult, DatabaseException>> batchRead(List<ReadRequest> batch); | ||
22 | + | ||
23 | + /** | ||
24 | + * Performs a write operation on the database. | ||
25 | + * @param request | ||
26 | + * @return write result. | ||
27 | + * @throws DatabaseException | ||
28 | + */ | ||
29 | + WriteResult write(WriteRequest request); | ||
30 | + | ||
31 | + /** | ||
32 | + * Performs a batch write operation on the database. | ||
33 | + * Batch write provides transactional semantics. Either all operations | ||
34 | + * succeed or none of them do. | ||
35 | + * @param batch batch of write requests to execute as a transaction. | ||
36 | + * @return result of executing the batch write operation. | ||
37 | + */ | ||
38 | + List<OptionalResult<WriteResult, DatabaseException>> batchWrite(List<WriteRequest> batch); | ||
39 | +} |
1 | +package org.onlab.onos.store.service; | ||
2 | + | ||
3 | +/** | ||
4 | + * Exception thrown when an operation (read or write) is requested for | ||
5 | + * a table that does not exist. | ||
6 | + */ | ||
7 | +@SuppressWarnings("serial") | ||
8 | +public class NoSuchTableException extends DatabaseException { | ||
9 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +package org.onlab.onos.store.service; | ||
2 | + | ||
3 | +/** | ||
4 | + * A container object which either has a result or an exception. | ||
5 | + * <p> | ||
6 | + * If a result is present, get() will return it otherwise get() will throw | ||
7 | + * the exception that was encountered in the process of generating the result. | ||
8 | + * | ||
9 | + * @param <R> type of result. | ||
10 | + * @param <E> exception encountered in generating the result. | ||
11 | + */ | ||
12 | +public interface OptionalResult<R, E extends Throwable> { | ||
13 | + | ||
14 | + /** | ||
15 | + * Returns the result. | ||
16 | + * @return result | ||
17 | + * @throws E if there is no valid result. | ||
18 | + */ | ||
19 | + public R get(); | ||
20 | + | ||
21 | + /** | ||
22 | + * Returns true if there is a valid result. | ||
23 | + * @return true is yes, false otherwise. | ||
24 | + */ | ||
25 | + public boolean hasValidResult(); | ||
26 | +} |
core/store/dist/src/main/java/org/onlab/onos/store/service/PreconditionFailedException.java
0 → 100644
1 | +package org.onlab.onos.store.service; | ||
2 | + | ||
3 | +/** | ||
4 | + * Exception that indicates a precondition failure. | ||
5 | + * <ul>Scenarios that can cause this exception: | ||
6 | + * <li>An operation that attempts to write a new value iff the current value is equal | ||
7 | + * to some specified value.</li> | ||
8 | + * <li>An operation that attempts to write a new value iff the current version | ||
9 | + * matches a specified value</li> | ||
10 | + * </ul> | ||
11 | + */ | ||
12 | +@SuppressWarnings("serial") | ||
13 | +public class PreconditionFailedException extends DatabaseException { | ||
14 | +} |
1 | +package org.onlab.onos.store.service; | ||
2 | + | ||
3 | +/** | ||
4 | + * Database read request. | ||
5 | + */ | ||
6 | +public class ReadRequest { | ||
7 | + | ||
8 | + private final String tableName; | ||
9 | + private final String key; | ||
10 | + | ||
11 | + public ReadRequest(String tableName, String key) { | ||
12 | + this.tableName = tableName; | ||
13 | + this.key = key; | ||
14 | + } | ||
15 | + | ||
16 | + /** | ||
17 | + * Return the name of the table. | ||
18 | + * @return table name. | ||
19 | + */ | ||
20 | + public String tableName() { | ||
21 | + return tableName; | ||
22 | + } | ||
23 | + | ||
24 | + /** | ||
25 | + * Returns the key. | ||
26 | + * @return key. | ||
27 | + */ | ||
28 | + public String key() { | ||
29 | + return key; | ||
30 | + } | ||
31 | + | ||
32 | + @Override | ||
33 | + public String toString() { | ||
34 | + return "ReadRequest [tableName=" + tableName + ", key=" + key + "]"; | ||
35 | + } | ||
36 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +package org.onlab.onos.store.service; | ||
2 | + | ||
3 | +import org.onlab.onos.store.service.impl.VersionedValue; | ||
4 | + | ||
5 | +/** | ||
6 | + * Database read result. | ||
7 | + */ | ||
8 | +public class ReadResult { | ||
9 | + | ||
10 | + private final String tableName; | ||
11 | + private final String key; | ||
12 | + private final VersionedValue value; | ||
13 | + | ||
14 | + public ReadResult(String tableName, String key, VersionedValue value) { | ||
15 | + this.tableName = tableName; | ||
16 | + this.key = key; | ||
17 | + this.value = value; | ||
18 | + } | ||
19 | + | ||
20 | + /** | ||
21 | + * Database table name. | ||
22 | + * @return | ||
23 | + */ | ||
24 | + public String tableName() { | ||
25 | + return tableName; | ||
26 | + } | ||
27 | + | ||
28 | + /** | ||
29 | + * Database table key. | ||
30 | + * @return key. | ||
31 | + */ | ||
32 | + public String key() { | ||
33 | + return key; | ||
34 | + } | ||
35 | + | ||
36 | + /** | ||
37 | + * value associated with the key. | ||
38 | + * @return non-null value if the table contains one, null otherwise. | ||
39 | + */ | ||
40 | + public VersionedValue value() { | ||
41 | + return value; | ||
42 | + } | ||
43 | +} |
1 | +package org.onlab.onos.store.service; | ||
2 | + | ||
3 | +import static com.google.common.base.Preconditions.checkArgument; | ||
4 | + | ||
5 | +import java.util.Objects; | ||
6 | + | ||
7 | +/** | ||
8 | + * Database write request. | ||
9 | + */ | ||
10 | +public class WriteRequest { | ||
11 | + | ||
12 | + private final String tableName; | ||
13 | + private final String key; | ||
14 | + private final byte[] newValue; | ||
15 | + private final long previousVersion; | ||
16 | + private final byte[] oldValue; | ||
17 | + | ||
18 | + public WriteRequest(String tableName, String key, byte[] newValue) { | ||
19 | + this(tableName, key, newValue, -1, null); | ||
20 | + } | ||
21 | + | ||
22 | + public WriteRequest(String tableName, String key, byte[] newValue, long previousVersion) { | ||
23 | + this(tableName, key, newValue, previousVersion, null); | ||
24 | + checkArgument(previousVersion >= 0); | ||
25 | + } | ||
26 | + | ||
27 | + public WriteRequest(String tableName, String key, byte[] newValue, byte[] oldValue) { | ||
28 | + this(tableName, key, newValue, -1, oldValue); | ||
29 | + } | ||
30 | + | ||
31 | + private WriteRequest(String tableName, String key, byte[] newValue, long previousVersion, byte[] oldValue) { | ||
32 | + | ||
33 | + checkArgument(tableName != null); | ||
34 | + checkArgument(key != null); | ||
35 | + checkArgument(newValue != null); | ||
36 | + | ||
37 | + this.tableName = tableName; | ||
38 | + this.key = key; | ||
39 | + this.newValue = newValue; | ||
40 | + this.previousVersion = previousVersion; | ||
41 | + this.oldValue = oldValue; | ||
42 | + } | ||
43 | + | ||
44 | + public String tableName() { | ||
45 | + return tableName; | ||
46 | + } | ||
47 | + | ||
48 | + public String key() { | ||
49 | + return key; | ||
50 | + } | ||
51 | + | ||
52 | + public byte[] newValue() { | ||
53 | + return newValue; | ||
54 | + } | ||
55 | + | ||
56 | + public long previousVersion() { | ||
57 | + return previousVersion; | ||
58 | + } | ||
59 | + | ||
60 | + public byte[] oldValue() { | ||
61 | + return oldValue; | ||
62 | + } | ||
63 | + | ||
64 | + @Override | ||
65 | + public String toString() { | ||
66 | + return "WriteRequest [tableName=" + tableName + ", key=" + key | ||
67 | + + ", newValue=" + newValue | ||
68 | + + ", previousVersion=" + previousVersion | ||
69 | + + ", oldValue=" + oldValue; | ||
70 | + } | ||
71 | + | ||
72 | + @Override | ||
73 | + public int hashCode() { | ||
74 | + return Objects.hash(key, tableName, previousVersion); | ||
75 | + } | ||
76 | + | ||
77 | + @Override | ||
78 | + public boolean equals(Object obj) { | ||
79 | + if (this == obj) { | ||
80 | + return true; | ||
81 | + } | ||
82 | + if (obj == null) { | ||
83 | + return false; | ||
84 | + } | ||
85 | + if (getClass() != obj.getClass()) { | ||
86 | + return false; | ||
87 | + } | ||
88 | + WriteRequest other = (WriteRequest) obj; | ||
89 | + return Objects.equals(this.key, other.key) && | ||
90 | + Objects.equals(this.tableName, other.tableName) && | ||
91 | + Objects.equals(this.previousVersion, other.previousVersion); | ||
92 | + } | ||
93 | +} |
1 | +package org.onlab.onos.store.service; | ||
2 | + | ||
3 | +import org.onlab.onos.store.service.impl.VersionedValue; | ||
4 | + | ||
5 | +/** | ||
6 | + * Database write result. | ||
7 | + */ | ||
8 | +public class WriteResult { | ||
9 | + | ||
10 | + private final String tableName; | ||
11 | + private final String key; | ||
12 | + private final VersionedValue previousValue; | ||
13 | + | ||
14 | + public WriteResult(String tableName, String key, VersionedValue previousValue) { | ||
15 | + this.tableName = tableName; | ||
16 | + this.key = key; | ||
17 | + this.previousValue = previousValue; | ||
18 | + } | ||
19 | + | ||
20 | + public String tableName() { | ||
21 | + return tableName; | ||
22 | + } | ||
23 | + | ||
24 | + public String key() { | ||
25 | + return key; | ||
26 | + } | ||
27 | + | ||
28 | + public VersionedValue previousValue() { | ||
29 | + return previousValue; | ||
30 | + } | ||
31 | +} |
1 | +package org.onlab.onos.store.service.impl; | ||
2 | + | ||
3 | +import java.util.Arrays; | ||
4 | +import java.util.List; | ||
5 | +import java.util.UUID; | ||
6 | +import java.util.concurrent.CompletableFuture; | ||
7 | +import java.util.concurrent.ExecutionException; | ||
8 | + | ||
9 | +import net.kuujo.copycat.protocol.Response.Status; | ||
10 | +import net.kuujo.copycat.protocol.SubmitRequest; | ||
11 | +import net.kuujo.copycat.protocol.SubmitResponse; | ||
12 | +import net.kuujo.copycat.spi.protocol.ProtocolClient; | ||
13 | + | ||
14 | +import org.apache.commons.lang3.RandomUtils; | ||
15 | +import org.onlab.netty.Endpoint; | ||
16 | +import org.onlab.netty.NettyMessagingService; | ||
17 | +import org.onlab.onos.store.service.DatabaseException; | ||
18 | +import org.onlab.onos.store.service.ReadRequest; | ||
19 | +import org.onlab.onos.store.service.WriteRequest; | ||
20 | + | ||
21 | +public class DatabaseClient { | ||
22 | + | ||
23 | + private final Endpoint copycatEp; | ||
24 | + ProtocolClient client; | ||
25 | + NettyMessagingService messagingService; | ||
26 | + | ||
27 | + public DatabaseClient(Endpoint copycatEp) { | ||
28 | + this.copycatEp = copycatEp; | ||
29 | + } | ||
30 | + | ||
31 | + private static String nextId() { | ||
32 | + return UUID.randomUUID().toString(); | ||
33 | + } | ||
34 | + | ||
35 | + public void activate() throws Exception { | ||
36 | + messagingService = new NettyMessagingService(RandomUtils.nextInt(10000, 40000)); | ||
37 | + messagingService.activate(); | ||
38 | + client = new NettyProtocolClient(copycatEp, messagingService); | ||
39 | + } | ||
40 | + | ||
41 | + public void deactivate() throws Exception { | ||
42 | + messagingService.deactivate(); | ||
43 | + } | ||
44 | + | ||
45 | + public boolean createTable(String tableName) { | ||
46 | + | ||
47 | + SubmitRequest request = | ||
48 | + new SubmitRequest( | ||
49 | + nextId(), | ||
50 | + "createTable", | ||
51 | + Arrays.asList(tableName)); | ||
52 | + CompletableFuture<SubmitResponse> future = client.submit(request); | ||
53 | + try { | ||
54 | + return (boolean) future.get().result(); | ||
55 | + } catch (InterruptedException | ExecutionException e) { | ||
56 | + throw new DatabaseException(e); | ||
57 | + } | ||
58 | + } | ||
59 | + | ||
60 | + public void dropTable(String tableName) { | ||
61 | + | ||
62 | + SubmitRequest request = | ||
63 | + new SubmitRequest( | ||
64 | + nextId(), | ||
65 | + "dropTable", | ||
66 | + Arrays.asList(tableName)); | ||
67 | + CompletableFuture<SubmitResponse> future = client.submit(request); | ||
68 | + try { | ||
69 | + if (future.get().status() == Status.OK) { | ||
70 | + throw new DatabaseException(future.get().toString()); | ||
71 | + } | ||
72 | + | ||
73 | + } catch (InterruptedException | ExecutionException e) { | ||
74 | + throw new DatabaseException(e); | ||
75 | + } | ||
76 | + } | ||
77 | + | ||
78 | + public void dropAllTables() { | ||
79 | + | ||
80 | + SubmitRequest request = | ||
81 | + new SubmitRequest( | ||
82 | + nextId(), | ||
83 | + "dropAllTables", | ||
84 | + Arrays.asList()); | ||
85 | + CompletableFuture<SubmitResponse> future = client.submit(request); | ||
86 | + try { | ||
87 | + if (future.get().status() != Status.OK) { | ||
88 | + throw new DatabaseException(future.get().toString()); | ||
89 | + } | ||
90 | + } catch (InterruptedException | ExecutionException e) { | ||
91 | + throw new DatabaseException(e); | ||
92 | + } | ||
93 | + } | ||
94 | + | ||
95 | + @SuppressWarnings("unchecked") | ||
96 | + public List<String> listTables() { | ||
97 | + | ||
98 | + SubmitRequest request = | ||
99 | + new SubmitRequest( | ||
100 | + nextId(), | ||
101 | + "listTables", | ||
102 | + Arrays.asList()); | ||
103 | + CompletableFuture<SubmitResponse> future = client.submit(request); | ||
104 | + try { | ||
105 | + return (List<String>) future.get().result(); | ||
106 | + } catch (InterruptedException | ExecutionException e) { | ||
107 | + throw new DatabaseException(e); | ||
108 | + } | ||
109 | + } | ||
110 | + | ||
111 | + @SuppressWarnings("unchecked") | ||
112 | + public List<InternalReadResult> batchRead(List<ReadRequest> requests) { | ||
113 | + | ||
114 | + SubmitRequest request = new SubmitRequest( | ||
115 | + nextId(), | ||
116 | + "read", | ||
117 | + Arrays.asList(requests)); | ||
118 | + | ||
119 | + CompletableFuture<SubmitResponse> future = client.submit(request); | ||
120 | + try { | ||
121 | + List<InternalReadResult> internalReadResults = (List<InternalReadResult>) future.get().result(); | ||
122 | + return internalReadResults; | ||
123 | + } catch (InterruptedException | ExecutionException e) { | ||
124 | + throw new DatabaseException(e); | ||
125 | + } | ||
126 | + } | ||
127 | + | ||
128 | + @SuppressWarnings("unchecked") | ||
129 | + public List<InternalWriteResult> batchWrite(List<WriteRequest> requests) { | ||
130 | + | ||
131 | + SubmitRequest request = new SubmitRequest( | ||
132 | + nextId(), | ||
133 | + "write", | ||
134 | + Arrays.asList(requests)); | ||
135 | + | ||
136 | + CompletableFuture<SubmitResponse> future = client.submit(request); | ||
137 | + try { | ||
138 | + List<InternalWriteResult> internalWriteResults = (List<InternalWriteResult>) future.get().result(); | ||
139 | + return internalWriteResults; | ||
140 | + } catch (InterruptedException | ExecutionException e) { | ||
141 | + throw new DatabaseException(e); | ||
142 | + } | ||
143 | + } | ||
144 | +} |
1 | +package org.onlab.onos.store.service.impl; | ||
2 | + | ||
3 | +import static org.slf4j.LoggerFactory.getLogger; | ||
4 | + | ||
5 | +import java.util.ArrayList; | ||
6 | +import java.util.Arrays; | ||
7 | +import java.util.List; | ||
8 | + | ||
9 | +import net.kuujo.copycat.Copycat; | ||
10 | +import net.kuujo.copycat.StateMachine; | ||
11 | +import net.kuujo.copycat.cluster.TcpCluster; | ||
12 | +import net.kuujo.copycat.cluster.TcpClusterConfig; | ||
13 | +import net.kuujo.copycat.cluster.TcpMember; | ||
14 | +import net.kuujo.copycat.log.ChronicleLog; | ||
15 | +import net.kuujo.copycat.log.Log; | ||
16 | + | ||
17 | +import org.apache.felix.scr.annotations.Activate; | ||
18 | +import org.apache.felix.scr.annotations.Component; | ||
19 | +import org.apache.felix.scr.annotations.Reference; | ||
20 | +import org.apache.felix.scr.annotations.ReferenceCardinality; | ||
21 | +import org.apache.felix.scr.annotations.Service; | ||
22 | +import org.onlab.netty.Endpoint; | ||
23 | +import org.onlab.onos.cluster.ClusterService; | ||
24 | +import org.onlab.onos.cluster.ControllerNode; | ||
25 | +import org.onlab.onos.store.service.DatabaseAdminService; | ||
26 | +import org.onlab.onos.store.service.DatabaseException; | ||
27 | +import org.onlab.onos.store.service.DatabaseService; | ||
28 | +import org.onlab.onos.store.service.NoSuchTableException; | ||
29 | +import org.onlab.onos.store.service.OptimisticLockException; | ||
30 | +import org.onlab.onos.store.service.OptionalResult; | ||
31 | +import org.onlab.onos.store.service.PreconditionFailedException; | ||
32 | +import org.onlab.onos.store.service.ReadRequest; | ||
33 | +import org.onlab.onos.store.service.ReadResult; | ||
34 | +import org.onlab.onos.store.service.WriteAborted; | ||
35 | +import org.onlab.onos.store.service.WriteRequest; | ||
36 | +import org.onlab.onos.store.service.WriteResult; | ||
37 | +import org.slf4j.Logger; | ||
38 | + | ||
39 | +import com.google.common.collect.Lists; | ||
40 | + | ||
41 | +/** | ||
42 | + * Strongly consistent and durable state management service based on | ||
43 | + * Copycat implementation of Raft consensus protocol. | ||
44 | + */ | ||
45 | +@Component(immediate = true) | ||
46 | +@Service | ||
47 | +public class DatabaseManager implements DatabaseService, DatabaseAdminService { | ||
48 | + | ||
49 | + private final Logger log = getLogger(getClass()); | ||
50 | + | ||
51 | + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | ||
52 | + ClusterService clusterService; | ||
53 | + | ||
54 | + public static final String LOG_FILE_PREFIX = "onos-copy-cat-log"; | ||
55 | + | ||
56 | + private Copycat copycat; | ||
57 | + private DatabaseClient client; | ||
58 | + | ||
59 | + @Activate | ||
60 | + public void activate() { | ||
61 | + TcpMember localMember = | ||
62 | + new TcpMember( | ||
63 | + clusterService.getLocalNode().ip().toString(), | ||
64 | + clusterService.getLocalNode().tcpPort()); | ||
65 | + List<TcpMember> remoteMembers = Lists.newArrayList(); | ||
66 | + | ||
67 | + for (ControllerNode node : clusterService.getNodes()) { | ||
68 | + TcpMember member = new TcpMember(node.ip().toString(), node.tcpPort()); | ||
69 | + if (!member.equals(localMember)) { | ||
70 | + remoteMembers.add(member); | ||
71 | + } | ||
72 | + } | ||
73 | + | ||
74 | + // Configure the cluster. | ||
75 | + TcpClusterConfig config = new TcpClusterConfig(); | ||
76 | + | ||
77 | + config.setLocalMember(localMember); | ||
78 | + config.setRemoteMembers(remoteMembers.toArray(new TcpMember[]{})); | ||
79 | + | ||
80 | + // Create the cluster. | ||
81 | + TcpCluster cluster = new TcpCluster(config); | ||
82 | + | ||
83 | + StateMachine stateMachine = new DatabaseStateMachine(); | ||
84 | + ControllerNode thisNode = clusterService.getLocalNode(); | ||
85 | + Log consensusLog = new ChronicleLog(LOG_FILE_PREFIX + "_" + thisNode.id()); | ||
86 | + | ||
87 | + copycat = new Copycat(stateMachine, consensusLog, cluster, new NettyProtocol()); | ||
88 | + copycat.start(); | ||
89 | + | ||
90 | + client = new DatabaseClient(new Endpoint(localMember.host(), localMember.port())); | ||
91 | + | ||
92 | + log.info("Started."); | ||
93 | + } | ||
94 | + | ||
95 | + @Activate | ||
96 | + public void deactivate() { | ||
97 | + copycat.stop(); | ||
98 | + } | ||
99 | + | ||
100 | + @Override | ||
101 | + public boolean createTable(String name) { | ||
102 | + return client.createTable(name); | ||
103 | + } | ||
104 | + | ||
105 | + @Override | ||
106 | + public void dropTable(String name) { | ||
107 | + client.dropTable(name); | ||
108 | + } | ||
109 | + | ||
110 | + @Override | ||
111 | + public void dropAllTables() { | ||
112 | + client.dropAllTables(); | ||
113 | + } | ||
114 | + | ||
115 | + @Override | ||
116 | + public List<String> listTables() { | ||
117 | + return client.listTables(); | ||
118 | + } | ||
119 | + | ||
120 | + @Override | ||
121 | + public ReadResult read(ReadRequest request) { | ||
122 | + return batchRead(Arrays.asList(request)).get(0).get(); | ||
123 | + } | ||
124 | + | ||
125 | + @Override | ||
126 | + public List<OptionalResult<ReadResult, DatabaseException>> batchRead( | ||
127 | + List<ReadRequest> batch) { | ||
128 | + List<OptionalResult<ReadResult, DatabaseException>> readResults = new ArrayList<>(batch.size()); | ||
129 | + for (InternalReadResult internalReadResult : client.batchRead(batch)) { | ||
130 | + if (internalReadResult.status() == InternalReadResult.Status.NO_SUCH_TABLE) { | ||
131 | + readResults.add(new DatabaseOperationResult<ReadResult, DatabaseException>( | ||
132 | + new NoSuchTableException())); | ||
133 | + } else { | ||
134 | + readResults.add(new DatabaseOperationResult<ReadResult, DatabaseException>( | ||
135 | + internalReadResult.result())); | ||
136 | + } | ||
137 | + } | ||
138 | + return readResults; | ||
139 | + } | ||
140 | + | ||
141 | + @Override | ||
142 | + public WriteResult write(WriteRequest request) { | ||
143 | + return batchWrite(Arrays.asList(request)).get(0).get(); | ||
144 | + } | ||
145 | + | ||
146 | + @Override | ||
147 | + public List<OptionalResult<WriteResult, DatabaseException>> batchWrite( | ||
148 | + List<WriteRequest> batch) { | ||
149 | + List<OptionalResult<WriteResult, DatabaseException>> writeResults = new ArrayList<>(batch.size()); | ||
150 | + for (InternalWriteResult internalWriteResult : client.batchWrite(batch)) { | ||
151 | + if (internalWriteResult.status() == InternalWriteResult.Status.NO_SUCH_TABLE) { | ||
152 | + writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( | ||
153 | + new NoSuchTableException())); | ||
154 | + } else if (internalWriteResult.status() == InternalWriteResult.Status.OPTIMISTIC_LOCK_FAILURE) { | ||
155 | + writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( | ||
156 | + new OptimisticLockException())); | ||
157 | + } else if (internalWriteResult.status() == InternalWriteResult.Status.PREVIOUS_VALUE_MISMATCH) { | ||
158 | + // TODO: throw a different exception? | ||
159 | + writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( | ||
160 | + new PreconditionFailedException())); | ||
161 | + } else if (internalWriteResult.status() == InternalWriteResult.Status.ABORTED) { | ||
162 | + writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( | ||
163 | + new WriteAborted())); | ||
164 | + } else { | ||
165 | + writeResults.add(new DatabaseOperationResult<WriteResult, DatabaseException>( | ||
166 | + internalWriteResult.result())); | ||
167 | + } | ||
168 | + } | ||
169 | + return writeResults; | ||
170 | + | ||
171 | + } | ||
172 | + | ||
173 | + private class DatabaseOperationResult<R, E extends DatabaseException> implements OptionalResult<R, E> { | ||
174 | + | ||
175 | + private final R result; | ||
176 | + private final DatabaseException exception; | ||
177 | + | ||
178 | + public DatabaseOperationResult(R result) { | ||
179 | + this.result = result; | ||
180 | + this.exception = null; | ||
181 | + } | ||
182 | + | ||
183 | + public DatabaseOperationResult(DatabaseException exception) { | ||
184 | + this.result = null; | ||
185 | + this.exception = exception; | ||
186 | + } | ||
187 | + | ||
188 | + @Override | ||
189 | + public R get() { | ||
190 | + if (result != null) { | ||
191 | + return result; | ||
192 | + } | ||
193 | + throw exception; | ||
194 | + } | ||
195 | + | ||
196 | + @Override | ||
197 | + public boolean hasValidResult() { | ||
198 | + return result != null; | ||
199 | + } | ||
200 | + | ||
201 | + @Override | ||
202 | + public String toString() { | ||
203 | + if (result != null) { | ||
204 | + return result.toString(); | ||
205 | + } else { | ||
206 | + return exception.toString(); | ||
207 | + } | ||
208 | + } | ||
209 | + } | ||
210 | +} |
core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseStateMachine.java
0 → 100644
1 | +package org.onlab.onos.store.service.impl; | ||
2 | + | ||
3 | +import java.util.ArrayList; | ||
4 | +import java.util.List; | ||
5 | +import java.util.Map; | ||
6 | +import java.util.Set; | ||
7 | + | ||
8 | +import net.kuujo.copycat.Command; | ||
9 | +import net.kuujo.copycat.Query; | ||
10 | +import net.kuujo.copycat.StateMachine; | ||
11 | + | ||
12 | +import org.onlab.onos.store.serializers.KryoSerializer; | ||
13 | +import org.onlab.onos.store.service.ReadRequest; | ||
14 | +import org.onlab.onos.store.service.ReadResult; | ||
15 | +import org.onlab.onos.store.service.WriteRequest; | ||
16 | +import org.onlab.onos.store.service.WriteResult; | ||
17 | +import org.onlab.util.KryoNamespace; | ||
18 | + | ||
19 | +import com.google.common.collect.Maps; | ||
20 | + | ||
21 | +public class DatabaseStateMachine implements StateMachine { | ||
22 | + | ||
23 | + public static final KryoSerializer SERIALIZER = new KryoSerializer() { | ||
24 | + @Override | ||
25 | + protected void setupKryoPool() { | ||
26 | + serializerPool = KryoNamespace.newBuilder() | ||
27 | + .register(VersionedValue.class) | ||
28 | + .register(State.class) | ||
29 | + .register(NettyProtocol.COMMON) | ||
30 | + .build() | ||
31 | + .populate(1); | ||
32 | + } | ||
33 | + }; | ||
34 | + | ||
35 | + private State state = new State(); | ||
36 | + | ||
37 | + @Command | ||
38 | + public boolean createTable(String tableName) { | ||
39 | + return state.getTables().putIfAbsent(tableName, Maps.newHashMap()) == null; | ||
40 | + } | ||
41 | + | ||
42 | + @Command | ||
43 | + public boolean dropTable(String tableName) { | ||
44 | + return state.getTables().remove(tableName) != null; | ||
45 | + } | ||
46 | + | ||
47 | + @Command | ||
48 | + public boolean dropAllTables() { | ||
49 | + state.getTables().clear(); | ||
50 | + return true; | ||
51 | + } | ||
52 | + | ||
53 | + @Query | ||
54 | + public Set<String> listTables() { | ||
55 | + return state.getTables().keySet(); | ||
56 | + } | ||
57 | + | ||
58 | + @Query | ||
59 | + public List<InternalReadResult> read(List<ReadRequest> requests) { | ||
60 | + List<InternalReadResult> results = new ArrayList<>(requests.size()); | ||
61 | + for (ReadRequest request : requests) { | ||
62 | + Map<String, VersionedValue> table = state.getTables().get(request.tableName()); | ||
63 | + if (table == null) { | ||
64 | + results.add(new InternalReadResult(InternalReadResult.Status.NO_SUCH_TABLE, null)); | ||
65 | + continue; | ||
66 | + } | ||
67 | + VersionedValue value = table.get(request.key()); | ||
68 | + results.add(new InternalReadResult( | ||
69 | + InternalReadResult.Status.OK, | ||
70 | + new ReadResult( | ||
71 | + request.tableName(), | ||
72 | + request.key(), | ||
73 | + value))); | ||
74 | + } | ||
75 | + return results; | ||
76 | + } | ||
77 | + | ||
78 | + @Command | ||
79 | + public List<InternalWriteResult> write(List<WriteRequest> requests) { | ||
80 | + boolean abort = false; | ||
81 | + List<InternalWriteResult.Status> validationResults = new ArrayList<>(requests.size()); | ||
82 | + for (WriteRequest request : requests) { | ||
83 | + Map<String, VersionedValue> table = state.getTables().get(request.tableName()); | ||
84 | + if (table == null) { | ||
85 | + validationResults.add(InternalWriteResult.Status.NO_SUCH_TABLE); | ||
86 | + abort = true; | ||
87 | + continue; | ||
88 | + } | ||
89 | + VersionedValue value = table.get(request.key()); | ||
90 | + if (value == null) { | ||
91 | + if (request.oldValue() != null) { | ||
92 | + validationResults.add(InternalWriteResult.Status.PREVIOUS_VALUE_MISMATCH); | ||
93 | + abort = true; | ||
94 | + continue; | ||
95 | + } else if (request.previousVersion() >= 0) { | ||
96 | + validationResults.add(InternalWriteResult.Status.OPTIMISTIC_LOCK_FAILURE); | ||
97 | + abort = true; | ||
98 | + continue; | ||
99 | + } | ||
100 | + } | ||
101 | + if (request.previousVersion() >= 0 && value.version() != request.previousVersion()) { | ||
102 | + validationResults.add(InternalWriteResult.Status.OPTIMISTIC_LOCK_FAILURE); | ||
103 | + abort = true; | ||
104 | + continue; | ||
105 | + } | ||
106 | + | ||
107 | + validationResults.add(InternalWriteResult.Status.OK); | ||
108 | + } | ||
109 | + | ||
110 | + List<InternalWriteResult> results = new ArrayList<>(requests.size()); | ||
111 | + | ||
112 | + if (abort) { | ||
113 | + for (InternalWriteResult.Status validationResult : validationResults) { | ||
114 | + if (validationResult == InternalWriteResult.Status.OK) { | ||
115 | + results.add(new InternalWriteResult(InternalWriteResult.Status.ABORTED, null)); | ||
116 | + } else { | ||
117 | + results.add(new InternalWriteResult(validationResult, null)); | ||
118 | + } | ||
119 | + } | ||
120 | + return results; | ||
121 | + } | ||
122 | + | ||
123 | + for (WriteRequest request : requests) { | ||
124 | + Map<String, VersionedValue> table = state.getTables().get(request.tableName()); | ||
125 | + synchronized (table) { | ||
126 | + VersionedValue previousValue = | ||
127 | + table.put(request.key(), new VersionedValue(request.newValue(), state.nextVersion())); | ||
128 | + results.add(new InternalWriteResult( | ||
129 | + InternalWriteResult.Status.OK, | ||
130 | + new WriteResult(request.tableName(), request.key(), previousValue))); | ||
131 | + } | ||
132 | + } | ||
133 | + return results; | ||
134 | + } | ||
135 | + | ||
136 | + public class State { | ||
137 | + | ||
138 | + private final Map<String, Map<String, VersionedValue>> tables = | ||
139 | + Maps.newHashMap(); | ||
140 | + private long versionCounter = 1; | ||
141 | + | ||
142 | + Map<String, Map<String, VersionedValue>> getTables() { | ||
143 | + return tables; | ||
144 | + } | ||
145 | + | ||
146 | + long nextVersion() { | ||
147 | + return versionCounter++; | ||
148 | + } | ||
149 | + } | ||
150 | + | ||
151 | + @Override | ||
152 | + public byte[] takeSnapshot() { | ||
153 | + try { | ||
154 | + return SERIALIZER.encode(state); | ||
155 | + } catch (Exception e) { | ||
156 | + e.printStackTrace(); | ||
157 | + return null; | ||
158 | + } | ||
159 | + } | ||
160 | + | ||
161 | + @Override | ||
162 | + public void installSnapshot(byte[] data) { | ||
163 | + try { | ||
164 | + this.state = SERIALIZER.decode(data); | ||
165 | + } catch (Exception e) { | ||
166 | + e.printStackTrace(); | ||
167 | + } | ||
168 | + } | ||
169 | +} |
1 | +package org.onlab.onos.store.service.impl; | ||
2 | + | ||
3 | +import org.onlab.onos.store.service.ReadResult; | ||
4 | + | ||
5 | +public class InternalReadResult { | ||
6 | + | ||
7 | + public enum Status { | ||
8 | + OK, | ||
9 | + NO_SUCH_TABLE | ||
10 | + } | ||
11 | + | ||
12 | + private final Status status; | ||
13 | + private final ReadResult result; | ||
14 | + | ||
15 | + public InternalReadResult(Status status, ReadResult result) { | ||
16 | + this.status = status; | ||
17 | + this.result = result; | ||
18 | + } | ||
19 | + | ||
20 | + public Status status() { | ||
21 | + return status; | ||
22 | + } | ||
23 | + | ||
24 | + public ReadResult result() { | ||
25 | + return result; | ||
26 | + } | ||
27 | + | ||
28 | + @Override | ||
29 | + public String toString() { | ||
30 | + return "InternalReadResult [status=" + status + ", result=" + result | ||
31 | + + "]"; | ||
32 | + } | ||
33 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +package org.onlab.onos.store.service.impl; | ||
2 | + | ||
3 | +import org.onlab.onos.store.service.WriteResult; | ||
4 | + | ||
5 | +public class InternalWriteResult { | ||
6 | + | ||
7 | + public enum Status { | ||
8 | + OK, | ||
9 | + ABORTED, | ||
10 | + NO_SUCH_TABLE, | ||
11 | + OPTIMISTIC_LOCK_FAILURE, | ||
12 | + PREVIOUS_VALUE_MISMATCH | ||
13 | + } | ||
14 | + | ||
15 | + private final Status status; | ||
16 | + private final WriteResult result; | ||
17 | + | ||
18 | + public InternalWriteResult(Status status, WriteResult result) { | ||
19 | + this.status = status; | ||
20 | + this.result = result; | ||
21 | + } | ||
22 | + | ||
23 | + public Status status() { | ||
24 | + return status; | ||
25 | + } | ||
26 | + | ||
27 | + public WriteResult result() { | ||
28 | + return result; | ||
29 | + } | ||
30 | +} |
1 | +package org.onlab.onos.store.service.impl; | ||
2 | + | ||
3 | +import java.util.ArrayList; | ||
4 | +import java.util.Arrays; | ||
5 | +import java.util.Collection; | ||
6 | +import java.util.HashMap; | ||
7 | +import java.util.HashSet; | ||
8 | +import java.util.LinkedList; | ||
9 | +import java.util.Vector; | ||
10 | + | ||
11 | +import net.kuujo.copycat.cluster.TcpClusterConfig; | ||
12 | +import net.kuujo.copycat.cluster.TcpMember; | ||
13 | +import net.kuujo.copycat.internal.log.ConfigurationEntry; | ||
14 | +import net.kuujo.copycat.internal.log.CopycatEntry; | ||
15 | +import net.kuujo.copycat.internal.log.OperationEntry; | ||
16 | +import net.kuujo.copycat.internal.log.SnapshotEntry; | ||
17 | +import net.kuujo.copycat.protocol.PingRequest; | ||
18 | +import net.kuujo.copycat.protocol.PingResponse; | ||
19 | +import net.kuujo.copycat.protocol.PollRequest; | ||
20 | +import net.kuujo.copycat.protocol.PollResponse; | ||
21 | +import net.kuujo.copycat.protocol.Response.Status; | ||
22 | +import net.kuujo.copycat.protocol.SubmitRequest; | ||
23 | +import net.kuujo.copycat.protocol.SubmitResponse; | ||
24 | +import net.kuujo.copycat.protocol.SyncRequest; | ||
25 | +import net.kuujo.copycat.protocol.SyncResponse; | ||
26 | +import net.kuujo.copycat.spi.protocol.Protocol; | ||
27 | +import net.kuujo.copycat.spi.protocol.ProtocolClient; | ||
28 | +import net.kuujo.copycat.spi.protocol.ProtocolServer; | ||
29 | + | ||
30 | +import org.onlab.onos.store.serializers.ImmutableListSerializer; | ||
31 | +import org.onlab.onos.store.serializers.ImmutableMapSerializer; | ||
32 | +import org.onlab.onos.store.serializers.ImmutableSetSerializer; | ||
33 | +import org.onlab.onos.store.serializers.KryoSerializer; | ||
34 | +import org.onlab.onos.store.service.ReadRequest; | ||
35 | +import org.onlab.onos.store.service.ReadResult; | ||
36 | +import org.onlab.onos.store.service.WriteRequest; | ||
37 | +import org.onlab.onos.store.service.WriteResult; | ||
38 | +import org.onlab.util.KryoNamespace; | ||
39 | + | ||
40 | +import com.esotericsoftware.kryo.Kryo; | ||
41 | +import com.esotericsoftware.kryo.io.Input; | ||
42 | +import com.esotericsoftware.kryo.serializers.CollectionSerializer; | ||
43 | +import com.google.common.collect.ImmutableList; | ||
44 | +import com.google.common.collect.ImmutableMap; | ||
45 | +import com.google.common.collect.ImmutableSet; | ||
46 | + | ||
47 | +/** | ||
48 | + * {@link Protocol} based on {@link org.onlab.netty.NettyMessagingService}. | ||
49 | + */ | ||
50 | +public class NettyProtocol implements Protocol<TcpMember> { | ||
51 | + | ||
52 | + public static final String COPYCAT_PING = "copycat-raft-consensus-ping"; | ||
53 | + public static final String COPYCAT_SYNC = "copycat-raft-consensus-sync"; | ||
54 | + public static final String COPYCAT_POLL = "copycat-raft-consensus-poll"; | ||
55 | + public static final String COPYCAT_SUBMIT = "copycat-raft-consensus-submit"; | ||
56 | + | ||
57 | + // TODO: make this configurable. | ||
58 | + public static final long RETRY_INTERVAL_MILLIS = 2000; | ||
59 | + | ||
60 | + private static final KryoNamespace COPYCAT = KryoNamespace.newBuilder() | ||
61 | + .register(PingRequest.class) | ||
62 | + .register(PingResponse.class) | ||
63 | + .register(PollRequest.class) | ||
64 | + .register(PollResponse.class) | ||
65 | + .register(SyncRequest.class) | ||
66 | + .register(SyncResponse.class) | ||
67 | + .register(SubmitRequest.class) | ||
68 | + .register(SubmitResponse.class) | ||
69 | + .register(Status.class) | ||
70 | + .register(ConfigurationEntry.class) | ||
71 | + .register(SnapshotEntry.class) | ||
72 | + .register(CopycatEntry.class) | ||
73 | + .register(OperationEntry.class) | ||
74 | + .register(TcpClusterConfig.class) | ||
75 | + .register(TcpMember.class) | ||
76 | + .build(); | ||
77 | + | ||
78 | + // TODO: Move to the right place. | ||
79 | + private static final KryoNamespace CRAFT = KryoNamespace.newBuilder() | ||
80 | + .register(ReadRequest.class) | ||
81 | + .register(WriteRequest.class) | ||
82 | + .register(InternalReadResult.class) | ||
83 | + .register(InternalWriteResult.class) | ||
84 | + .register(InternalReadResult.Status.class) | ||
85 | + .register(WriteResult.class) | ||
86 | + .register(ReadResult.class) | ||
87 | + .register(InternalWriteResult.Status.class) | ||
88 | + .register(VersionedValue.class) | ||
89 | + .build(); | ||
90 | + | ||
91 | + public static final KryoNamespace COMMON = KryoNamespace.newBuilder() | ||
92 | + .register(Arrays.asList().getClass(), new CollectionSerializer() { | ||
93 | + @Override | ||
94 | + @SuppressWarnings("rawtypes") | ||
95 | + protected Collection<?> create(Kryo kryo, Input input, Class<Collection> type) { | ||
96 | + return new ArrayList(); | ||
97 | + } | ||
98 | + }) | ||
99 | + .register(ImmutableMap.class, new ImmutableMapSerializer()) | ||
100 | + .register(ImmutableList.class, new ImmutableListSerializer()) | ||
101 | + .register(ImmutableSet.class, new ImmutableSetSerializer()) | ||
102 | + .register( | ||
103 | + Vector.class, | ||
104 | + ArrayList.class, | ||
105 | + Arrays.asList().getClass(), | ||
106 | + HashMap.class, | ||
107 | + HashSet.class, | ||
108 | + LinkedList.class, | ||
109 | + byte[].class) | ||
110 | + .build(); | ||
111 | + | ||
112 | + public static final KryoSerializer SERIALIZER = new KryoSerializer() { | ||
113 | + @Override | ||
114 | + protected void setupKryoPool() { | ||
115 | + serializerPool = KryoNamespace.newBuilder() | ||
116 | + .register(COPYCAT) | ||
117 | + .register(COMMON) | ||
118 | + .register(CRAFT) | ||
119 | + .build() | ||
120 | + .populate(1); | ||
121 | + } | ||
122 | + }; | ||
123 | + | ||
124 | + private NettyProtocolServer server = null; | ||
125 | + | ||
126 | + // FIXME: This is a total hack.Assumes | ||
127 | + // ProtocolServer is initialized before ProtocolClient | ||
128 | + protected NettyProtocolServer getServer() { | ||
129 | + if (server == null) { | ||
130 | + throw new IllegalStateException("ProtocolServer is not initialized yet!"); | ||
131 | + } | ||
132 | + return server; | ||
133 | + } | ||
134 | + | ||
135 | + @Override | ||
136 | + public ProtocolServer createServer(TcpMember member) { | ||
137 | + server = new NettyProtocolServer(member); | ||
138 | + return server; | ||
139 | + } | ||
140 | + | ||
141 | + @Override | ||
142 | + public ProtocolClient createClient(TcpMember member) { | ||
143 | + return new NettyProtocolClient(this, member); | ||
144 | + } | ||
145 | +} |
1 | +package org.onlab.onos.store.service.impl; | ||
2 | + | ||
3 | +import static org.slf4j.LoggerFactory.getLogger; | ||
4 | + | ||
5 | +import java.io.IOException; | ||
6 | +import java.util.concurrent.CompletableFuture; | ||
7 | +import java.util.concurrent.ExecutionException; | ||
8 | +import java.util.concurrent.ScheduledExecutorService; | ||
9 | +import java.util.concurrent.ScheduledThreadPoolExecutor; | ||
10 | +import java.util.concurrent.ThreadFactory; | ||
11 | +import java.util.concurrent.TimeUnit; | ||
12 | +import java.util.concurrent.TimeoutException; | ||
13 | + | ||
14 | +import net.kuujo.copycat.cluster.TcpMember; | ||
15 | +import net.kuujo.copycat.protocol.PingRequest; | ||
16 | +import net.kuujo.copycat.protocol.PingResponse; | ||
17 | +import net.kuujo.copycat.protocol.PollRequest; | ||
18 | +import net.kuujo.copycat.protocol.PollResponse; | ||
19 | +import net.kuujo.copycat.protocol.SubmitRequest; | ||
20 | +import net.kuujo.copycat.protocol.SubmitResponse; | ||
21 | +import net.kuujo.copycat.protocol.SyncRequest; | ||
22 | +import net.kuujo.copycat.protocol.SyncResponse; | ||
23 | +import net.kuujo.copycat.spi.protocol.ProtocolClient; | ||
24 | + | ||
25 | +import org.onlab.netty.Endpoint; | ||
26 | +import org.onlab.netty.NettyMessagingService; | ||
27 | +import org.slf4j.Logger; | ||
28 | + | ||
29 | +import com.google.common.util.concurrent.ThreadFactoryBuilder; | ||
30 | + | ||
31 | +/** | ||
32 | + * {@link NettyMessagingService} based Copycat protocol client. | ||
33 | + */ | ||
34 | +public class NettyProtocolClient implements ProtocolClient { | ||
35 | + | ||
36 | + private final Logger log = getLogger(getClass()); | ||
37 | + private static final ThreadFactory THREAD_FACTORY = | ||
38 | + new ThreadFactoryBuilder().setNameFormat("copycat-netty-messaging-%d").build(); | ||
39 | + | ||
40 | + // Remote endpoint, this client instance is used | ||
41 | + // for communicating with. | ||
42 | + private final Endpoint remoteEp; | ||
43 | + private final NettyMessagingService messagingService; | ||
44 | + | ||
45 | + // TODO: Is 10 the right number of threads? | ||
46 | + private static final ScheduledExecutorService THREAD_POOL = | ||
47 | + new ScheduledThreadPoolExecutor(10, THREAD_FACTORY); | ||
48 | + | ||
49 | + public NettyProtocolClient(NettyProtocol protocol, TcpMember member) { | ||
50 | + this(new Endpoint(member.host(), member.port()), protocol.getServer().getNettyMessagingService()); | ||
51 | + } | ||
52 | + | ||
53 | + public NettyProtocolClient(Endpoint remoteEp, NettyMessagingService messagingService) { | ||
54 | + this.remoteEp = remoteEp; | ||
55 | + this.messagingService = messagingService; | ||
56 | + } | ||
57 | + | ||
58 | + @Override | ||
59 | + public CompletableFuture<PingResponse> ping(PingRequest request) { | ||
60 | + return requestReply(request); | ||
61 | + } | ||
62 | + | ||
63 | + @Override | ||
64 | + public CompletableFuture<SyncResponse> sync(SyncRequest request) { | ||
65 | + return requestReply(request); | ||
66 | + } | ||
67 | + | ||
68 | + @Override | ||
69 | + public CompletableFuture<PollResponse> poll(PollRequest request) { | ||
70 | + return requestReply(request); | ||
71 | + } | ||
72 | + | ||
73 | + @Override | ||
74 | + public CompletableFuture<SubmitResponse> submit(SubmitRequest request) { | ||
75 | + return requestReply(request); | ||
76 | + } | ||
77 | + | ||
78 | + @Override | ||
79 | + public CompletableFuture<Void> connect() { | ||
80 | + return CompletableFuture.completedFuture(null); | ||
81 | + } | ||
82 | + | ||
83 | + @Override | ||
84 | + public CompletableFuture<Void> close() { | ||
85 | + return CompletableFuture.completedFuture(null); | ||
86 | + } | ||
87 | + | ||
88 | + public <I> String messageType(I input) { | ||
89 | + Class<?> clazz = input.getClass(); | ||
90 | + if (clazz.equals(PollRequest.class)) { | ||
91 | + return NettyProtocol.COPYCAT_POLL; | ||
92 | + } else if (clazz.equals(SyncRequest.class)) { | ||
93 | + return NettyProtocol.COPYCAT_SYNC; | ||
94 | + } else if (clazz.equals(SubmitRequest.class)) { | ||
95 | + return NettyProtocol.COPYCAT_SUBMIT; | ||
96 | + } else if (clazz.equals(PingRequest.class)) { | ||
97 | + return NettyProtocol.COPYCAT_PING; | ||
98 | + } else { | ||
99 | + throw new IllegalArgumentException("Unknown class " + clazz.getName()); | ||
100 | + } | ||
101 | + | ||
102 | + } | ||
103 | + | ||
104 | + private <I, O> CompletableFuture<O> requestReply(I request) { | ||
105 | + CompletableFuture<O> future = new CompletableFuture<>(); | ||
106 | + THREAD_POOL.schedule(new RPCTask<I, O>(request, future), 0, TimeUnit.MILLISECONDS); | ||
107 | + return future; | ||
108 | + } | ||
109 | + | ||
110 | + private class RPCTask<I, O> implements Runnable { | ||
111 | + | ||
112 | + private final String messageType; | ||
113 | + private final byte[] payload; | ||
114 | + | ||
115 | + private final CompletableFuture<O> future; | ||
116 | + | ||
117 | + public RPCTask(I request, CompletableFuture<O> future) { | ||
118 | + this.messageType = messageType(request); | ||
119 | + this.payload = NettyProtocol.SERIALIZER.encode(request); | ||
120 | + this.future = future; | ||
121 | + } | ||
122 | + | ||
123 | + @Override | ||
124 | + public void run() { | ||
125 | + try { | ||
126 | + byte[] response = messagingService | ||
127 | + .sendAndReceive(remoteEp, messageType, payload) | ||
128 | + .get(NettyProtocol.RETRY_INTERVAL_MILLIS, TimeUnit.MILLISECONDS); | ||
129 | + future.complete(NettyProtocol.SERIALIZER.decode(response)); | ||
130 | + | ||
131 | + } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) { | ||
132 | + if (messageType.equals(NettyProtocol.COPYCAT_SYNC) || | ||
133 | + messageType.equals(NettyProtocol.COPYCAT_PING)) { | ||
134 | + log.warn("Request to {} failed. Will retry " | ||
135 | + + "in {} ms", remoteEp, NettyProtocol.RETRY_INTERVAL_MILLIS); | ||
136 | + THREAD_POOL.schedule( | ||
137 | + this, | ||
138 | + NettyProtocol.RETRY_INTERVAL_MILLIS, | ||
139 | + TimeUnit.MILLISECONDS); | ||
140 | + } else { | ||
141 | + future.completeExceptionally(e); | ||
142 | + } | ||
143 | + } catch (Exception e) { | ||
144 | + future.completeExceptionally(e); | ||
145 | + } | ||
146 | + } | ||
147 | + } | ||
148 | +} |
1 | +package org.onlab.onos.store.service.impl; | ||
2 | + | ||
3 | +import static org.slf4j.LoggerFactory.getLogger; | ||
4 | + | ||
5 | +import java.io.IOException; | ||
6 | +import java.util.concurrent.CompletableFuture; | ||
7 | + | ||
8 | +import net.kuujo.copycat.cluster.TcpMember; | ||
9 | +import net.kuujo.copycat.protocol.PingRequest; | ||
10 | +import net.kuujo.copycat.protocol.PollRequest; | ||
11 | +import net.kuujo.copycat.protocol.RequestHandler; | ||
12 | +import net.kuujo.copycat.protocol.SubmitRequest; | ||
13 | +import net.kuujo.copycat.protocol.SyncRequest; | ||
14 | +import net.kuujo.copycat.spi.protocol.ProtocolServer; | ||
15 | + | ||
16 | +import org.onlab.netty.Message; | ||
17 | +import org.onlab.netty.MessageHandler; | ||
18 | +import org.onlab.netty.NettyMessagingService; | ||
19 | +import org.slf4j.Logger; | ||
20 | + | ||
21 | +/** | ||
22 | + * {@link NettyMessagingService} based Copycat protocol server. | ||
23 | + */ | ||
24 | +public class NettyProtocolServer implements ProtocolServer { | ||
25 | + | ||
26 | + private final Logger log = getLogger(getClass()); | ||
27 | + | ||
28 | + private final NettyMessagingService messagingService; | ||
29 | + private RequestHandler handler; | ||
30 | + | ||
31 | + | ||
32 | + public NettyProtocolServer(TcpMember member) { | ||
33 | + messagingService = new NettyMessagingService(member.host(), member.port()); | ||
34 | + | ||
35 | + messagingService.registerHandler(NettyProtocol.COPYCAT_PING, new CopycatMessageHandler<PingRequest>()); | ||
36 | + messagingService.registerHandler(NettyProtocol.COPYCAT_SYNC, new CopycatMessageHandler<SyncRequest>()); | ||
37 | + messagingService.registerHandler(NettyProtocol.COPYCAT_POLL, new CopycatMessageHandler<PollRequest>()); | ||
38 | + messagingService.registerHandler(NettyProtocol.COPYCAT_SUBMIT, new CopycatMessageHandler<SubmitRequest>()); | ||
39 | + } | ||
40 | + | ||
41 | + protected NettyMessagingService getNettyMessagingService() { | ||
42 | + return messagingService; | ||
43 | + } | ||
44 | + | ||
45 | + @Override | ||
46 | + public void requestHandler(RequestHandler handler) { | ||
47 | + this.handler = handler; | ||
48 | + } | ||
49 | + | ||
50 | + @Override | ||
51 | + public CompletableFuture<Void> listen() { | ||
52 | + try { | ||
53 | + messagingService.activate(); | ||
54 | + return CompletableFuture.completedFuture(null); | ||
55 | + } catch (Exception e) { | ||
56 | + CompletableFuture<Void> future = new CompletableFuture<>(); | ||
57 | + future.completeExceptionally(e); | ||
58 | + return future; | ||
59 | + } | ||
60 | + } | ||
61 | + | ||
62 | + @Override | ||
63 | + public CompletableFuture<Void> close() { | ||
64 | + CompletableFuture<Void> future = new CompletableFuture<>(); | ||
65 | + try { | ||
66 | + messagingService.deactivate(); | ||
67 | + future.complete(null); | ||
68 | + return future; | ||
69 | + } catch (Exception e) { | ||
70 | + future.completeExceptionally(e); | ||
71 | + return future; | ||
72 | + } | ||
73 | + } | ||
74 | + | ||
75 | + private class CopycatMessageHandler<T> implements MessageHandler { | ||
76 | + | ||
77 | + @Override | ||
78 | + public void handle(Message message) throws IOException { | ||
79 | + T request = NettyProtocol.SERIALIZER.decode(message.payload()); | ||
80 | + if (request.getClass().equals(PingRequest.class)) { | ||
81 | + handler.ping((PingRequest) request).whenComplete((response, error) -> { | ||
82 | + try { | ||
83 | + message.respond(NettyProtocol.SERIALIZER.encode(response)); | ||
84 | + } catch (Exception e) { | ||
85 | + log.error("Failed to respond to ping request", e); | ||
86 | + } | ||
87 | + }); | ||
88 | + } else if (request.getClass().equals(PollRequest.class)) { | ||
89 | + handler.poll((PollRequest) request).whenComplete((response, error) -> { | ||
90 | + try { | ||
91 | + message.respond(NettyProtocol.SERIALIZER.encode(response)); | ||
92 | + } catch (Exception e) { | ||
93 | + log.error("Failed to respond to poll request", e); | ||
94 | + } | ||
95 | + }); | ||
96 | + } else if (request.getClass().equals(SyncRequest.class)) { | ||
97 | + handler.sync((SyncRequest) request).whenComplete((response, error) -> { | ||
98 | + try { | ||
99 | + message.respond(NettyProtocol.SERIALIZER.encode(response)); | ||
100 | + } catch (Exception e) { | ||
101 | + log.error("Failed to respond to sync request", e); | ||
102 | + } | ||
103 | + }); | ||
104 | + } else if (request.getClass().equals(SubmitRequest.class)) { | ||
105 | + handler.submit((SubmitRequest) request).whenComplete((response, error) -> { | ||
106 | + try { | ||
107 | + message.respond(NettyProtocol.SERIALIZER.encode(response)); | ||
108 | + } catch (Exception e) { | ||
109 | + log.error("Failed to respond to submit request", e); | ||
110 | + } | ||
111 | + }); | ||
112 | + } | ||
113 | + } | ||
114 | + } | ||
115 | +} |
1 | +package org.onlab.onos.store.service.impl; | ||
2 | + | ||
3 | +import java.util.Arrays; | ||
4 | + | ||
5 | +/** | ||
6 | + * Wrapper object that holds the object (as byte array) and its version. | ||
7 | + */ | ||
8 | +public class VersionedValue { | ||
9 | + | ||
10 | + private final byte[] value; | ||
11 | + private final long version; | ||
12 | + | ||
13 | + /** | ||
14 | + * Creates a new instance with the specified value and version. | ||
15 | + * @param value | ||
16 | + * @param version | ||
17 | + */ | ||
18 | + public VersionedValue(byte[] value, long version) { | ||
19 | + this.value = value; | ||
20 | + this.version = version; | ||
21 | + } | ||
22 | + | ||
23 | + /** | ||
24 | + * Returns the value. | ||
25 | + * @return value. | ||
26 | + */ | ||
27 | + public byte[] value() { | ||
28 | + return value; | ||
29 | + } | ||
30 | + | ||
31 | + /** | ||
32 | + * Returns the version. | ||
33 | + * @return version. | ||
34 | + */ | ||
35 | + public long version() { | ||
36 | + return version; | ||
37 | + } | ||
38 | + | ||
39 | + @Override | ||
40 | + public String toString() { | ||
41 | + return "VersionedValue [value=" + Arrays.toString(value) + ", version=" | ||
42 | + + version + "]"; | ||
43 | + } | ||
44 | +} |
... | @@ -32,9 +32,15 @@ public class RoleReplyInfo { | ... | @@ -32,9 +32,15 @@ public class RoleReplyInfo { |
32 | this.genId = genId; | 32 | this.genId = genId; |
33 | this.xid = xid; | 33 | this.xid = xid; |
34 | } | 34 | } |
35 | - public RoleState getRole() { return role; } | 35 | + public RoleState getRole() { |
36 | - public U64 getGenId() { return genId; } | 36 | + return role; |
37 | - public long getXid() { return xid; } | 37 | + } |
38 | + public U64 getGenId() { | ||
39 | + return genId; | ||
40 | + } | ||
41 | + public long getXid() { | ||
42 | + return xid; | ||
43 | + } | ||
38 | @Override | 44 | @Override |
39 | public String toString() { | 45 | public String toString() { |
40 | return "[Role:" + role + " GenId:" + genId + " Xid:" + xid + "]"; | 46 | return "[Role:" + role + " GenId:" + genId + " Xid:" + xid + "]"; | ... | ... |
... | @@ -347,7 +347,7 @@ class RoleManager implements RoleHandler { | ... | @@ -347,7 +347,7 @@ class RoleManager implements RoleHandler { |
347 | 347 | ||
348 | RoleState role = null; | 348 | RoleState role = null; |
349 | OFNiciraControllerRole ncr = nrr.getRole(); | 349 | OFNiciraControllerRole ncr = nrr.getRole(); |
350 | - switch(ncr) { | 350 | + switch (ncr) { |
351 | case ROLE_MASTER: | 351 | case ROLE_MASTER: |
352 | role = RoleState.MASTER; | 352 | role = RoleState.MASTER; |
353 | break; | 353 | break; |
... | @@ -383,7 +383,7 @@ class RoleManager implements RoleHandler { | ... | @@ -383,7 +383,7 @@ class RoleManager implements RoleHandler { |
383 | throws SwitchStateException { | 383 | throws SwitchStateException { |
384 | OFControllerRole cr = rrmsg.getRole(); | 384 | OFControllerRole cr = rrmsg.getRole(); |
385 | RoleState role = null; | 385 | RoleState role = null; |
386 | - switch(cr) { | 386 | + switch (cr) { |
387 | case ROLE_EQUAL: | 387 | case ROLE_EQUAL: |
388 | role = RoleState.EQUAL; | 388 | role = RoleState.EQUAL; |
389 | break; | 389 | break; | ... | ... |
... | @@ -414,7 +414,7 @@ | ... | @@ -414,7 +414,7 @@ |
414 | <plugin> | 414 | <plugin> |
415 | <groupId>org.apache.felix</groupId> | 415 | <groupId>org.apache.felix</groupId> |
416 | <artifactId>maven-bundle-plugin</artifactId> | 416 | <artifactId>maven-bundle-plugin</artifactId> |
417 | - <version>2.3.7</version> | 417 | + <version>2.5.3</version> |
418 | <extensions>true</extensions> | 418 | <extensions>true</extensions> |
419 | </plugin> | 419 | </plugin> |
420 | 420 | ||
... | @@ -493,6 +493,12 @@ | ... | @@ -493,6 +493,12 @@ |
493 | <artifactId>onos-build-conf</artifactId> | 493 | <artifactId>onos-build-conf</artifactId> |
494 | <version>1.0</version> | 494 | <version>1.0</version> |
495 | </dependency> | 495 | </dependency> |
496 | + <!-- For Java 8 lambda support--> | ||
497 | + <dependency> | ||
498 | + <groupId>com.puppycrawl.tools</groupId> | ||
499 | + <artifactId>checkstyle</artifactId> | ||
500 | + <version>5.9</version> | ||
501 | + </dependency> | ||
496 | </dependencies> | 502 | </dependencies> |
497 | <configuration> | 503 | <configuration> |
498 | <configLocation>onos/checkstyle.xml</configLocation> | 504 | <configLocation>onos/checkstyle.xml</configLocation> | ... | ... |
-
Please register or login to post a comment