Madan Jampani

Support for expiring Database entries

Registering database entry expiration tracker with DatabaseStateMachine

Support for publishing database state machine snapshot installation events.
Expiry tracker will listen to these events to bootstrap its local state.

Change-Id: I8bf22c8d7bab38624341350ccc083c5ca2fcb117
...@@ -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 }
...@@ -418,4 +432,4 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService { ...@@ -418,4 +432,4 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
418 } 432 }
419 return null; 433 return null;
420 } 434 }
421 -}
...\ No newline at end of file ...\ No newline at end of file
435 +}
......
...@@ -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 - }
87 - return true;
88 - }
89 - return false;
90 } 85 }
91 86
92 @Command 87 @Command
93 - public boolean createTable(String tableName, int expirationTimeMillis) { 88 + public boolean createTable(String tableName, int ttlMillis) {
94 - Map<String, VersionedValue> existingTable = 89 + TableMetadata metadata = new TableMetadata(tableName, ttlMillis);
95 - state.getTables().putIfAbsent(tableName, Maps.newHashMap()); 90 + return createTable(metadata);
96 - if (existingTable == null) { 91 + }
97 - for (DatabaseUpdateEventListener listener : listeners) { 92 +
98 - listener.tableCreated(tableName, expirationTimeMillis); 93 + private boolean createTable(TableMetadata metadata) {
99 - } 94 + Map<String, VersionedValue> existingTable = state.getTable(metadata.tableName());
100 - return true; 95 + if (existingTable != null) {
96 + return false;
101 } 97 }
102 - return false; 98 + state.createTable(metadata);
99 + for (DatabaseUpdateEventListener listener : listeners) {
100 + listener.tableCreated(metadata);
101 + }
102 + return true;
103 } 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);
42 -}
...\ No newline at end of file ...\ No newline at end of file
43 +
44 + /**
45 + * Notifies listeners of a snapshot installation event.
46 + * @param snapshotState installed snapshot state.
47 + */
48 + public void snapshotInstalled(DatabaseStateMachine.State snapshotState);
49 +}
......