Yuta Higuchi
Committed by Gerrit Code Review

Merge "Support for expiring Database entries"

...@@ -20,6 +20,16 @@ public interface DatabaseAdminService { ...@@ -20,6 +20,16 @@ public interface DatabaseAdminService {
20 public boolean createTable(String name); 20 public boolean createTable(String name);
21 21
22 /** 22 /**
23 + * Creates a new table where last update time will be used to track and expire old entries.
24 + * Table creation is idempotent. Attempting to create a table
25 + * that already exists will be a noop.
26 + * @param name table name.
27 + * @param ttlMillis total duration in millis since last update time when entries will be expired.
28 + * @return true if the table was created by this call, false otherwise.
29 + */
30 + public boolean createTable(String name, int ttlMillis);
31 +
32 + /**
23 * Lists all the tables in the database. 33 * Lists all the tables in the database.
24 * @return list of table names. 34 * @return list of table names.
25 */ 35 */
......
...@@ -35,6 +35,16 @@ public class DatabaseClient { ...@@ -35,6 +35,16 @@ public class DatabaseClient {
35 } 35 }
36 } 36 }
37 37
38 + public boolean createTable(String tableName, int ttlMillis) {
39 +
40 + CompletableFuture<Boolean> future = copycat.submit("createTable", tableName, ttlMillis);
41 + try {
42 + return future.get();
43 + } catch (InterruptedException | ExecutionException e) {
44 + throw new DatabaseException(e);
45 + }
46 + }
47 +
38 public void dropTable(String tableName) { 48 public void dropTable(String tableName) {
39 49
40 CompletableFuture<Void> future = copycat.submit("dropTable", tableName); 50 CompletableFuture<Void> future = copycat.submit("dropTable", tableName);
......
...@@ -30,12 +30,14 @@ import net.kuujo.copycat.cluster.Member; ...@@ -30,12 +30,14 @@ import net.kuujo.copycat.cluster.Member;
30 import net.kuujo.copycat.event.EventHandler; 30 import net.kuujo.copycat.event.EventHandler;
31 import net.kuujo.copycat.event.LeaderElectEvent; 31 import net.kuujo.copycat.event.LeaderElectEvent;
32 32
33 -import org.onlab.onos.cluster.ClusterService; 33 +import org.onlab.onos.cluster.ControllerNode;
34 import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService; 34 import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
35 import org.onlab.onos.store.cluster.messaging.ClusterMessage; 35 import org.onlab.onos.store.cluster.messaging.ClusterMessage;
36 import org.onlab.onos.store.cluster.messaging.MessageSubject; 36 import org.onlab.onos.store.cluster.messaging.MessageSubject;
37 import org.onlab.onos.store.service.DatabaseService; 37 import org.onlab.onos.store.service.DatabaseService;
38 import org.onlab.onos.store.service.VersionedValue; 38 import org.onlab.onos.store.service.VersionedValue;
39 +import org.onlab.onos.store.service.impl.DatabaseStateMachine.State;
40 +import org.onlab.onos.store.service.impl.DatabaseStateMachine.TableMetadata;
39 import org.slf4j.Logger; 41 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory; 42 import org.slf4j.LoggerFactory;
41 43
...@@ -52,23 +54,34 @@ public class DatabaseEntryExpirationTracker implements ...@@ -52,23 +54,34 @@ public class DatabaseEntryExpirationTracker implements
52 public static final MessageSubject DATABASE_UPDATES = new MessageSubject( 54 public static final MessageSubject DATABASE_UPDATES = new MessageSubject(
53 "database-update-event"); 55 "database-update-event");
54 56
55 - private DatabaseService databaseService; 57 + private final DatabaseService databaseService;
56 - private ClusterService cluster; 58 + private final ClusterCommunicationService clusterCommunicator;
57 - private ClusterCommunicationService clusterCommunicator;
58 59
59 private final Member localMember; 60 private final Member localMember;
61 + private final ControllerNode localNode;
60 private final AtomicBoolean isLocalMemberLeader = new AtomicBoolean(false); 62 private final AtomicBoolean isLocalMemberLeader = new AtomicBoolean(false);
61 63
62 private final Map<String, Map<DatabaseRow, VersionedValue>> tableEntryExpirationMap = new HashMap<>(); 64 private final Map<String, Map<DatabaseRow, VersionedValue>> tableEntryExpirationMap = new HashMap<>();
63 65
64 private final ExpirationListener<DatabaseRow, VersionedValue> expirationObserver = new ExpirationObserver(); 66 private final ExpirationListener<DatabaseRow, VersionedValue> expirationObserver = new ExpirationObserver();
65 67
66 - DatabaseEntryExpirationTracker(Member localMember) { 68 + DatabaseEntryExpirationTracker(
69 + Member localMember,
70 + ControllerNode localNode,
71 + ClusterCommunicationService clusterCommunicator,
72 + DatabaseService databaseService) {
67 this.localMember = localMember; 73 this.localMember = localMember;
74 + this.localNode = localNode;
75 + this.clusterCommunicator = clusterCommunicator;
76 + this.databaseService = databaseService;
68 } 77 }
69 78
70 @Override 79 @Override
71 public void tableModified(TableModificationEvent event) { 80 public void tableModified(TableModificationEvent event) {
81 + if (!tableEntryExpirationMap.containsKey(event.tableName())) {
82 + return;
83 + }
84 +
72 DatabaseRow row = new DatabaseRow(event.tableName(), event.key()); 85 DatabaseRow row = new DatabaseRow(event.tableName(), event.key());
73 Map<DatabaseRow, VersionedValue> map = tableEntryExpirationMap 86 Map<DatabaseRow, VersionedValue> map = tableEntryExpirationMap
74 .get(event.tableName()); 87 .get(event.tableName());
...@@ -77,8 +90,8 @@ public class DatabaseEntryExpirationTracker implements ...@@ -77,8 +90,8 @@ public class DatabaseEntryExpirationTracker implements
77 case ROW_DELETED: 90 case ROW_DELETED:
78 if (isLocalMemberLeader.get()) { 91 if (isLocalMemberLeader.get()) {
79 try { 92 try {
80 - clusterCommunicator.broadcast(new ClusterMessage(cluster 93 + clusterCommunicator.broadcast(new ClusterMessage(
81 - .getLocalNode().id(), DATABASE_UPDATES, 94 + localNode.id(), DATABASE_UPDATES,
82 DatabaseStateMachine.SERIALIZER.encode(event))); 95 DatabaseStateMachine.SERIALIZER.encode(event)));
83 } catch (IOException e) { 96 } catch (IOException e) {
84 log.error( 97 log.error(
...@@ -97,12 +110,10 @@ public class DatabaseEntryExpirationTracker implements ...@@ -97,12 +110,10 @@ public class DatabaseEntryExpirationTracker implements
97 } 110 }
98 111
99 @Override 112 @Override
100 - public void tableCreated(String tableName, int expirationTimeMillis) { 113 + public void tableCreated(TableMetadata metadata) {
101 - // make this explicit instead of relying on a negative value 114 + if (metadata.expireOldEntries()) {
102 - // to indicate no expiration. 115 + tableEntryExpirationMap.put(metadata.tableName(), ExpiringMap.builder()
103 - if (expirationTimeMillis > 0) { 116 + .expiration(metadata.ttlMillis(), TimeUnit.SECONDS)
104 - tableEntryExpirationMap.put(tableName, ExpiringMap.builder()
105 - .expiration(expirationTimeMillis, TimeUnit.SECONDS)
106 .expirationListener(expirationObserver) 117 .expirationListener(expirationObserver)
107 // FIXME: make the expiration policy configurable. 118 // FIXME: make the expiration policy configurable.
108 .expirationPolicy(ExpirationPolicy.CREATED).build()); 119 .expirationPolicy(ExpirationPolicy.CREATED).build());
...@@ -188,4 +199,28 @@ public class DatabaseEntryExpirationTracker implements ...@@ -188,4 +199,28 @@ public class DatabaseEntryExpirationTracker implements
188 return Objects.hash(tableName, key); 199 return Objects.hash(tableName, key);
189 } 200 }
190 } 201 }
202 +
203 + @Override
204 + public void snapshotInstalled(State state) {
205 + if (!tableEntryExpirationMap.isEmpty()) {
206 + return;
207 + }
208 + for (String tableName : state.getTableNames()) {
209 +
210 + TableMetadata metadata = state.getTableMetadata(tableName);
211 + if (!metadata.expireOldEntries()) {
212 + continue;
213 + }
214 +
215 + Map<DatabaseRow, VersionedValue> tableExpirationMap = ExpiringMap.builder()
216 + .expiration(metadata.ttlMillis(), TimeUnit.MILLISECONDS)
217 + .expirationListener(expirationObserver)
218 + .expirationPolicy(ExpirationPolicy.CREATED).build();
219 + for (Map.Entry<String, VersionedValue> entry : state.getTable(tableName).entrySet()) {
220 + tableExpirationMap.put(new DatabaseRow(tableName, entry.getKey()), entry.getValue());
221 + }
222 +
223 + tableEntryExpirationMap.put(tableName, tableExpirationMap);
224 + }
225 + }
191 } 226 }
......
...@@ -14,7 +14,6 @@ import java.util.concurrent.CountDownLatch; ...@@ -14,7 +14,6 @@ import java.util.concurrent.CountDownLatch;
14 import java.util.concurrent.TimeUnit; 14 import java.util.concurrent.TimeUnit;
15 15
16 import net.kuujo.copycat.Copycat; 16 import net.kuujo.copycat.Copycat;
17 -import net.kuujo.copycat.StateMachine;
18 import net.kuujo.copycat.cluster.ClusterConfig; 17 import net.kuujo.copycat.cluster.ClusterConfig;
19 import net.kuujo.copycat.cluster.Member; 18 import net.kuujo.copycat.cluster.Member;
20 import net.kuujo.copycat.cluster.TcpCluster; 19 import net.kuujo.copycat.cluster.TcpCluster;
...@@ -34,6 +33,7 @@ import org.onlab.onos.cluster.ClusterService; ...@@ -34,6 +33,7 @@ import org.onlab.onos.cluster.ClusterService;
34 import org.onlab.onos.cluster.ControllerNode; 33 import org.onlab.onos.cluster.ControllerNode;
35 import org.onlab.onos.cluster.DefaultControllerNode; 34 import org.onlab.onos.cluster.DefaultControllerNode;
36 import org.onlab.onos.cluster.NodeId; 35 import org.onlab.onos.cluster.NodeId;
36 +import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
37 import org.onlab.onos.store.service.BatchReadRequest; 37 import org.onlab.onos.store.service.BatchReadRequest;
38 import org.onlab.onos.store.service.BatchReadResult; 38 import org.onlab.onos.store.service.BatchReadResult;
39 import org.onlab.onos.store.service.BatchWriteRequest; 39 import org.onlab.onos.store.service.BatchWriteRequest;
...@@ -65,6 +65,9 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -65,6 +65,9 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
65 protected ClusterService clusterService; 65 protected ClusterService clusterService;
66 66
67 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 67 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
68 + protected ClusterCommunicationService clusterCommunicator;
69 +
70 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
68 protected DatabaseProtocolService copycatMessagingProtocol; 71 protected DatabaseProtocolService copycatMessagingProtocol;
69 72
70 // FIXME: point to appropriate path 73 // FIXME: point to appropriate path
...@@ -158,7 +161,13 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -158,7 +161,13 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
158 log.info("Starting cluster: {}", cluster); 161 log.info("Starting cluster: {}", cluster);
159 162
160 163
161 - StateMachine stateMachine = new DatabaseStateMachine(); 164 + DatabaseStateMachine stateMachine = new DatabaseStateMachine();
165 + stateMachine.addEventListener(
166 + new DatabaseEntryExpirationTracker(
167 + clusterConfig.getLocalMember(),
168 + clusterService.getLocalNode(),
169 + clusterCommunicator,
170 + this));
162 Log consensusLog = new MapDBLog(LOG_FILE_PREFIX + localNode.id(), 171 Log consensusLog = new MapDBLog(LOG_FILE_PREFIX + localNode.id(),
163 ClusterMessagingProtocol.SERIALIZER); 172 ClusterMessagingProtocol.SERIALIZER);
164 173
...@@ -183,6 +192,11 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -183,6 +192,11 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
183 } 192 }
184 193
185 @Override 194 @Override
195 + public boolean createTable(String name, int ttlMillis) {
196 + return client.createTable(name, ttlMillis);
197 + }
198 +
199 + @Override
186 public void dropTable(String name) { 200 public void dropTable(String name) {
187 client.dropTable(name); 201 client.dropTable(name);
188 } 202 }
......
...@@ -30,9 +30,10 @@ import org.onlab.onos.store.service.WriteStatus; ...@@ -30,9 +30,10 @@ import org.onlab.onos.store.service.WriteStatus;
30 import org.onlab.util.KryoNamespace; 30 import org.onlab.util.KryoNamespace;
31 import org.slf4j.Logger; 31 import org.slf4j.Logger;
32 32
33 -import com.google.common.collect.ImmutableList; 33 +import com.google.common.collect.ImmutableSet;
34 import com.google.common.collect.Lists; 34 import com.google.common.collect.Lists;
35 import com.google.common.collect.Maps; 35 import com.google.common.collect.Maps;
36 +import com.google.common.collect.Sets;
36 import com.google.common.io.ByteStreams; 37 import com.google.common.io.ByteStreams;
37 38
38 /** 39 /**
...@@ -57,6 +58,7 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -57,6 +58,7 @@ public class DatabaseStateMachine implements StateMachine {
57 serializerPool = KryoNamespace.newBuilder() 58 serializerPool = KryoNamespace.newBuilder()
58 .register(VersionedValue.class) 59 .register(VersionedValue.class)
59 .register(State.class) 60 .register(State.class)
61 + .register(TableMetadata.class)
60 .register(BatchReadRequest.class) 62 .register(BatchReadRequest.class)
61 .register(BatchWriteRequest.class) 63 .register(BatchWriteRequest.class)
62 .register(ReadStatus.class) 64 .register(ReadStatus.class)
...@@ -69,7 +71,7 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -69,7 +71,7 @@ public class DatabaseStateMachine implements StateMachine {
69 } 71 }
70 }; 72 };
71 73
72 - private final List<DatabaseUpdateEventListener> listeners = Lists.newLinkedList(); 74 + private final Set<DatabaseUpdateEventListener> listeners = Sets.newIdentityHashSet();
73 75
74 // durable internal state of the database. 76 // durable internal state of the database.
75 private State state = new State(); 77 private State state = new State();
...@@ -78,34 +80,31 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -78,34 +80,31 @@ public class DatabaseStateMachine implements StateMachine {
78 80
79 @Command 81 @Command
80 public boolean createTable(String tableName) { 82 public boolean createTable(String tableName) {
81 - Map<String, VersionedValue> existingTable = 83 + TableMetadata metadata = new TableMetadata(tableName);
82 - state.getTables().putIfAbsent(tableName, Maps.newHashMap()); 84 + return createTable(metadata);
83 - if (existingTable == null) {
84 - for (DatabaseUpdateEventListener listener : listeners) {
85 - listener.tableCreated(tableName, Integer.MAX_VALUE);
86 } 85 }
87 - return true; 86 +
87 + @Command
88 + public boolean createTable(String tableName, int ttlMillis) {
89 + TableMetadata metadata = new TableMetadata(tableName, ttlMillis);
90 + return createTable(metadata);
88 } 91 }
92 +
93 + private boolean createTable(TableMetadata metadata) {
94 + Map<String, VersionedValue> existingTable = state.getTable(metadata.tableName());
95 + if (existingTable != null) {
89 return false; 96 return false;
90 } 97 }
91 - 98 + state.createTable(metadata);
92 - @Command
93 - public boolean createTable(String tableName, int expirationTimeMillis) {
94 - Map<String, VersionedValue> existingTable =
95 - state.getTables().putIfAbsent(tableName, Maps.newHashMap());
96 - if (existingTable == null) {
97 for (DatabaseUpdateEventListener listener : listeners) { 99 for (DatabaseUpdateEventListener listener : listeners) {
98 - listener.tableCreated(tableName, expirationTimeMillis); 100 + listener.tableCreated(metadata);
99 } 101 }
100 return true; 102 return true;
101 } 103 }
102 - return false;
103 - }
104 104
105 @Command 105 @Command
106 public boolean dropTable(String tableName) { 106 public boolean dropTable(String tableName) {
107 - Map<String, VersionedValue> table = state.getTables().remove(tableName); 107 + if (state.removeTable(tableName)) {
108 - if (table != null) {
109 for (DatabaseUpdateEventListener listener : listeners) { 108 for (DatabaseUpdateEventListener listener : listeners) {
110 listener.tableDeleted(tableName); 109 listener.tableDeleted(tableName);
111 } 110 }
...@@ -116,8 +115,8 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -116,8 +115,8 @@ public class DatabaseStateMachine implements StateMachine {
116 115
117 @Command 116 @Command
118 public boolean dropAllTables() { 117 public boolean dropAllTables() {
119 - Set<String> tableNames = state.getTables().keySet(); 118 + Set<String> tableNames = state.getTableNames();
120 - state.getTables().clear(); 119 + state.removeAllTables();
121 for (DatabaseUpdateEventListener listener : listeners) { 120 for (DatabaseUpdateEventListener listener : listeners) {
122 for (String tableName : tableNames) { 121 for (String tableName : tableNames) {
123 listener.tableDeleted(tableName); 122 listener.tableDeleted(tableName);
...@@ -127,15 +126,15 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -127,15 +126,15 @@ public class DatabaseStateMachine implements StateMachine {
127 } 126 }
128 127
129 @Query 128 @Query
130 - public List<String> listTables() { 129 + public Set<String> listTables() {
131 - return ImmutableList.copyOf(state.getTables().keySet()); 130 + return ImmutableSet.copyOf(state.getTableNames());
132 } 131 }
133 132
134 @Query 133 @Query
135 public List<ReadResult> read(BatchReadRequest batchRequest) { 134 public List<ReadResult> read(BatchReadRequest batchRequest) {
136 List<ReadResult> results = new ArrayList<>(batchRequest.batchSize()); 135 List<ReadResult> results = new ArrayList<>(batchRequest.batchSize());
137 for (ReadRequest request : batchRequest.getAsList()) { 136 for (ReadRequest request : batchRequest.getAsList()) {
138 - Map<String, VersionedValue> table = state.getTables().get(request.tableName()); 137 + Map<String, VersionedValue> table = state.getTable(request.tableName());
139 if (table == null) { 138 if (table == null) {
140 results.add(new ReadResult(ReadStatus.NO_SUCH_TABLE, request.tableName(), request.key(), null)); 139 results.add(new ReadResult(ReadStatus.NO_SUCH_TABLE, request.tableName(), request.key(), null));
141 continue; 140 continue;
...@@ -186,7 +185,7 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -186,7 +185,7 @@ public class DatabaseStateMachine implements StateMachine {
186 boolean abort = false; 185 boolean abort = false;
187 List<WriteStatus> validationResults = new ArrayList<>(batchRequest.batchSize()); 186 List<WriteStatus> validationResults = new ArrayList<>(batchRequest.batchSize());
188 for (WriteRequest request : batchRequest.getAsList()) { 187 for (WriteRequest request : batchRequest.getAsList()) {
189 - Map<String, VersionedValue> table = state.getTables().get(request.tableName()); 188 + Map<String, VersionedValue> table = state.getTable(request.tableName());
190 if (table == null) { 189 if (table == null) {
191 validationResults.add(WriteStatus.NO_SUCH_TABLE); 190 validationResults.add(WriteStatus.NO_SUCH_TABLE);
192 abort = true; 191 abort = true;
...@@ -218,7 +217,7 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -218,7 +217,7 @@ public class DatabaseStateMachine implements StateMachine {
218 217
219 // apply changes 218 // apply changes
220 for (WriteRequest request : batchRequest.getAsList()) { 219 for (WriteRequest request : batchRequest.getAsList()) {
221 - Map<String, VersionedValue> table = state.getTables().get(request.tableName()); 220 + Map<String, VersionedValue> table = state.getTable(request.tableName());
222 221
223 TableModificationEvent tableModificationEvent = null; 222 TableModificationEvent tableModificationEvent = null;
224 // FIXME: If this method could be called by multiple thread, 223 // FIXME: If this method could be called by multiple thread,
...@@ -274,19 +273,78 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -274,19 +273,78 @@ public class DatabaseStateMachine implements StateMachine {
274 return results; 273 return results;
275 } 274 }
276 275
277 - public class State { 276 + public static class State {
278 277
279 - private final Map<String, Map<String, VersionedValue>> tables = 278 + private final Map<String, TableMetadata> tableMetadata = Maps.newHashMap();
280 - Maps.newHashMap(); 279 + private final Map<String, Map<String, VersionedValue>> tableData = Maps.newHashMap();
281 private long versionCounter = 1; 280 private long versionCounter = 1;
282 281
283 - Map<String, Map<String, VersionedValue>> getTables() { 282 + public Map<String, VersionedValue> getTable(String tableName) {
284 - return tables; 283 + return tableData.get(tableName);
284 + }
285 +
286 + void createTable(TableMetadata metadata) {
287 + tableMetadata.put(metadata.tableName, metadata);
288 + tableData.put(metadata.tableName, Maps.newHashMap());
289 + }
290 +
291 + TableMetadata getTableMetadata(String tableName) {
292 + return tableMetadata.get(tableName);
285 } 293 }
286 294
287 long nextVersion() { 295 long nextVersion() {
288 return versionCounter++; 296 return versionCounter++;
289 } 297 }
298 +
299 + Set<String> getTableNames() {
300 + return ImmutableSet.copyOf(tableMetadata.keySet());
301 + }
302 +
303 +
304 + boolean removeTable(String tableName) {
305 + if (!tableMetadata.containsKey(tableName)) {
306 + return false;
307 + }
308 + tableMetadata.remove(tableName);
309 + tableData.remove(tableName);
310 + return true;
311 + }
312 +
313 + void removeAllTables() {
314 + tableMetadata.clear();
315 + tableData.clear();
316 + }
317 + }
318 +
319 + public static class TableMetadata {
320 + private final String tableName;
321 + private final boolean expireOldEntries;
322 + private final int ttlMillis;
323 +
324 + public TableMetadata(String tableName) {
325 + this.tableName = tableName;
326 + this.expireOldEntries = false;
327 + this.ttlMillis = Integer.MAX_VALUE;
328 +
329 + }
330 +
331 + public TableMetadata(String tableName, int ttlMillis) {
332 + this.tableName = tableName;
333 + this.expireOldEntries = true;
334 + this.ttlMillis = ttlMillis;
335 + }
336 +
337 + public String tableName() {
338 + return tableName;
339 + }
340 +
341 + public boolean expireOldEntries() {
342 + return expireOldEntries;
343 + }
344 +
345 + public int ttlMillis() {
346 + return ttlMillis;
347 + }
290 } 348 }
291 349
292 @Override 350 @Override
...@@ -319,13 +377,30 @@ public class DatabaseStateMachine implements StateMachine { ...@@ -319,13 +377,30 @@ public class DatabaseStateMachine implements StateMachine {
319 } else { 377 } else {
320 this.state = SERIALIZER.decode(data); 378 this.state = SERIALIZER.decode(data);
321 } 379 }
380 +
381 + // FIXME: synchronize.
382 + for (DatabaseUpdateEventListener listener : listeners) {
383 + listener.snapshotInstalled(state);
384 + }
322 } catch (Exception e) { 385 } catch (Exception e) {
323 log.error("Failed to install from snapshot", e); 386 log.error("Failed to install from snapshot", e);
324 throw new SnapshotException(e); 387 throw new SnapshotException(e);
325 } 388 }
326 } 389 }
327 390
391 + /**
392 + * Adds specified DatabaseUpdateEventListener.
393 + * @param listener listener to add
394 + */
328 public void addEventListener(DatabaseUpdateEventListener listener) { 395 public void addEventListener(DatabaseUpdateEventListener listener) {
329 listeners.add(listener); 396 listeners.add(listener);
330 } 397 }
398 +
399 + /**
400 + * Removes specified DatabaseUpdateEventListener.
401 + * @param listener listener to remove
402 + */
403 + public void removeEventListener(DatabaseUpdateEventListener listener) {
404 + listeners.remove(listener);
405 + }
331 } 406 }
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
16 16
17 package org.onlab.onos.store.service.impl; 17 package org.onlab.onos.store.service.impl;
18 18
19 +import org.onlab.onos.store.service.impl.DatabaseStateMachine.TableMetadata;
20 +
19 /** 21 /**
20 * Interface of database update event listeners. 22 * Interface of database update event listeners.
21 */ 23 */
...@@ -29,14 +31,19 @@ public interface DatabaseUpdateEventListener { ...@@ -29,14 +31,19 @@ public interface DatabaseUpdateEventListener {
29 31
30 /** 32 /**
31 * Notifies listeners of a table created event. 33 * Notifies listeners of a table created event.
32 - * @param tableName name of the table created 34 + * @param metadata metadata for the created table.
33 - * @param expirationTimeMillis TTL for entries added to the table (measured since last update time)
34 */ 35 */
35 - public void tableCreated(String tableName, int expirationTimeMillis); 36 + public void tableCreated(TableMetadata metadata);
36 37
37 /** 38 /**
38 * Notifies listeners of a table deleted event. 39 * Notifies listeners of a table deleted event.
39 * @param tableName name of the table deleted 40 * @param tableName name of the table deleted
40 */ 41 */
41 public void tableDeleted(String tableName); 42 public void tableDeleted(String tableName);
43 +
44 + /**
45 + * Notifies listeners of a snapshot installation event.
46 + * @param snapshotState installed snapshot state.
47 + */
48 + public void snapshotInstalled(DatabaseStateMachine.State snapshotState);
42 } 49 }
......