blob: 2fbef0beec2778fec513aa6a46826b49ced6be80 [file] [log] [blame]
Madan Jampani08822c42014-11-04 17:17:46 -08001package org.onlab.onos.store.service.impl;
2
Madan Jampania88d1f52014-11-14 16:45:24 -08003import static org.onlab.util.Tools.namedThreads;
Yuta HIGUCHI39ae5502014-11-05 16:42:12 -08004import static org.slf4j.LoggerFactory.getLogger;
5
Yuta HIGUCHIa7680a32014-11-06 22:17:37 -08006import java.io.ByteArrayInputStream;
7import java.io.ByteArrayOutputStream;
Madan Jampani08822c42014-11-04 17:17:46 -08008import java.util.ArrayList;
Yuta HIGUCHI361664e2014-11-06 17:28:47 -08009import java.util.Arrays;
Madan Jampani08822c42014-11-04 17:17:46 -080010import java.util.List;
11import java.util.Map;
Madan Jampani12390c12014-11-12 00:35:56 -080012import java.util.Set;
Madan Jampania88d1f52014-11-14 16:45:24 -080013import java.util.concurrent.ExecutorService;
14import java.util.concurrent.Executors;
Yuta HIGUCHIa7680a32014-11-06 22:17:37 -080015import java.util.zip.DeflaterOutputStream;
16import java.util.zip.InflaterInputStream;
Yuta HIGUCHI361664e2014-11-06 17:28:47 -080017
Madan Jampani08822c42014-11-04 17:17:46 -080018import net.kuujo.copycat.Command;
19import net.kuujo.copycat.Query;
20import net.kuujo.copycat.StateMachine;
21
Madan Jampani12390c12014-11-12 00:35:56 -080022import org.onlab.onos.store.cluster.messaging.MessageSubject;
Madan Jampani08822c42014-11-04 17:17:46 -080023import org.onlab.onos.store.serializers.KryoSerializer;
Madan Jampani12390c12014-11-12 00:35:56 -080024import org.onlab.onos.store.service.BatchReadRequest;
25import org.onlab.onos.store.service.BatchWriteRequest;
Madan Jampani08822c42014-11-04 17:17:46 -080026import org.onlab.onos.store.service.ReadRequest;
27import org.onlab.onos.store.service.ReadResult;
Madan Jampani12390c12014-11-12 00:35:56 -080028import org.onlab.onos.store.service.ReadStatus;
Madan Jampani37c2e702014-11-04 18:11:10 -080029import org.onlab.onos.store.service.VersionedValue;
Madan Jampani08822c42014-11-04 17:17:46 -080030import org.onlab.onos.store.service.WriteRequest;
31import org.onlab.onos.store.service.WriteResult;
Madan Jampani12390c12014-11-12 00:35:56 -080032import org.onlab.onos.store.service.WriteStatus;
Madan Jampani08822c42014-11-04 17:17:46 -080033import org.onlab.util.KryoNamespace;
Yuta HIGUCHI39ae5502014-11-05 16:42:12 -080034import org.slf4j.Logger;
Madan Jampani08822c42014-11-04 17:17:46 -080035
Madan Jampanif5d263b2014-11-13 10:04:40 -080036import com.google.common.base.MoreObjects;
Yuta HIGUCHI841c0b62014-11-13 20:27:14 -080037import com.google.common.collect.ImmutableMap;
Madan Jampanidef2c652014-11-12 13:50:10 -080038import com.google.common.collect.ImmutableSet;
Madan Jampani932c6ba2014-11-12 01:36:04 -080039import com.google.common.collect.Lists;
Madan Jampani08822c42014-11-04 17:17:46 -080040import com.google.common.collect.Maps;
Madan Jampanidef2c652014-11-12 13:50:10 -080041import com.google.common.collect.Sets;
Yuta HIGUCHIa7680a32014-11-06 22:17:37 -080042import com.google.common.io.ByteStreams;
Madan Jampani08822c42014-11-04 17:17:46 -080043
Madan Jampani686fa182014-11-04 23:16:27 -080044/**
45 * StateMachine whose transitions are coordinated/replicated
46 * by Raft consensus.
47 * Each Raft cluster member has a instance of this state machine that is
48 * independently updated in lock step once there is consensus
49 * on the next transition.
50 */
Madan Jampani08822c42014-11-04 17:17:46 -080051public class DatabaseStateMachine implements StateMachine {
52
Yuta HIGUCHI39ae5502014-11-05 16:42:12 -080053 private final Logger log = getLogger(getClass());
54
Madan Jampania88d1f52014-11-14 16:45:24 -080055 private final ExecutorService updatesExecutor =
56 Executors.newSingleThreadExecutor(namedThreads("database-statemachine-updates"));
57
Madan Jampani12390c12014-11-12 00:35:56 -080058 // message subject for database update notifications.
Madan Jampani44e6a542014-11-12 01:06:51 -080059 public static final MessageSubject DATABASE_UPDATE_EVENTS =
Madan Jampani12390c12014-11-12 00:35:56 -080060 new MessageSubject("database-update-events");
61
Yuta HIGUCHI361664e2014-11-06 17:28:47 -080062 // serializer used for snapshot
Madan Jampani08822c42014-11-04 17:17:46 -080063 public static final KryoSerializer SERIALIZER = new KryoSerializer() {
64 @Override
65 protected void setupKryoPool() {
66 serializerPool = KryoNamespace.newBuilder()
67 .register(VersionedValue.class)
68 .register(State.class)
Madan Jampanidef2c652014-11-12 13:50:10 -080069 .register(TableMetadata.class)
Madan Jampani12390c12014-11-12 00:35:56 -080070 .register(BatchReadRequest.class)
71 .register(BatchWriteRequest.class)
72 .register(ReadStatus.class)
73 .register(WriteStatus.class)
74 // TODO: Move this out ?
75 .register(TableModificationEvent.class)
Madan Jampanif5d263b2014-11-13 10:04:40 -080076 .register(TableModificationEvent.Type.class)
Madan Jampani9b19a822014-11-04 21:37:13 -080077 .register(ClusterMessagingProtocol.COMMON)
Madan Jampani08822c42014-11-04 17:17:46 -080078 .build()
79 .populate(1);
80 }
81 };
82
Madan Jampanidef2c652014-11-12 13:50:10 -080083 private final Set<DatabaseUpdateEventListener> listeners = Sets.newIdentityHashSet();
Madan Jampani12390c12014-11-12 00:35:56 -080084
85 // durable internal state of the database.
Madan Jampani08822c42014-11-04 17:17:46 -080086 private State state = new State();
87
Yuta HIGUCHIa7680a32014-11-06 22:17:37 -080088 private boolean compressSnapshot = false;
89
Madan Jampani08822c42014-11-04 17:17:46 -080090 @Command
91 public boolean createTable(String tableName) {
Madan Jampanidef2c652014-11-12 13:50:10 -080092 TableMetadata metadata = new TableMetadata(tableName);
93 return createTable(metadata);
Madan Jampani12390c12014-11-12 00:35:56 -080094 }
95
96 @Command
Madan Jampania88d1f52014-11-14 16:45:24 -080097 public boolean createTable(String tableName, Integer ttlMillis) {
Madan Jampanidef2c652014-11-12 13:50:10 -080098 TableMetadata metadata = new TableMetadata(tableName, ttlMillis);
99 return createTable(metadata);
100 }
101
102 private boolean createTable(TableMetadata metadata) {
103 Map<String, VersionedValue> existingTable = state.getTable(metadata.tableName());
104 if (existingTable != null) {
105 return false;
Madan Jampani12390c12014-11-12 00:35:56 -0800106 }
Madan Jampanidef2c652014-11-12 13:50:10 -0800107 state.createTable(metadata);
Madan Jampania88d1f52014-11-14 16:45:24 -0800108
109 updatesExecutor.submit(new Runnable() {
110 @Override
111 public void run() {
112 for (DatabaseUpdateEventListener listener : listeners) {
113 listener.tableCreated(metadata);
114 }
115 }
116 });
117
Madan Jampanidef2c652014-11-12 13:50:10 -0800118 return true;
Madan Jampani08822c42014-11-04 17:17:46 -0800119 }
120
121 @Command
122 public boolean dropTable(String tableName) {
Madan Jampanidef2c652014-11-12 13:50:10 -0800123 if (state.removeTable(tableName)) {
Madan Jampania88d1f52014-11-14 16:45:24 -0800124
125 updatesExecutor.submit(new Runnable() {
126 @Override
127 public void run() {
128 for (DatabaseUpdateEventListener listener : listeners) {
129 listener.tableDeleted(tableName);
130 }
131 }
132 });
133
Madan Jampani12390c12014-11-12 00:35:56 -0800134 return true;
135 }
136 return false;
Madan Jampani08822c42014-11-04 17:17:46 -0800137 }
138
139 @Command
140 public boolean dropAllTables() {
Madan Jampanidef2c652014-11-12 13:50:10 -0800141 Set<String> tableNames = state.getTableNames();
142 state.removeAllTables();
Madan Jampania88d1f52014-11-14 16:45:24 -0800143
144 updatesExecutor.submit(new Runnable() {
145 @Override
146 public void run() {
147 for (DatabaseUpdateEventListener listener : listeners) {
148 for (String tableName : tableNames) {
149 listener.tableDeleted(tableName);
150 }
151 }
Madan Jampani12390c12014-11-12 00:35:56 -0800152 }
Madan Jampania88d1f52014-11-14 16:45:24 -0800153 });
154
Madan Jampani08822c42014-11-04 17:17:46 -0800155 return true;
156 }
157
158 @Query
Madan Jampanidef2c652014-11-12 13:50:10 -0800159 public Set<String> listTables() {
160 return ImmutableSet.copyOf(state.getTableNames());
Madan Jampani08822c42014-11-04 17:17:46 -0800161 }
162
163 @Query
Madan Jampani12390c12014-11-12 00:35:56 -0800164 public List<ReadResult> read(BatchReadRequest batchRequest) {
165 List<ReadResult> results = new ArrayList<>(batchRequest.batchSize());
166 for (ReadRequest request : batchRequest.getAsList()) {
Madan Jampanidef2c652014-11-12 13:50:10 -0800167 Map<String, VersionedValue> table = state.getTable(request.tableName());
Madan Jampani08822c42014-11-04 17:17:46 -0800168 if (table == null) {
Madan Jampani12390c12014-11-12 00:35:56 -0800169 results.add(new ReadResult(ReadStatus.NO_SUCH_TABLE, request.tableName(), request.key(), null));
Madan Jampani08822c42014-11-04 17:17:46 -0800170 continue;
171 }
Yuta HIGUCHI3bd8cdc2014-11-05 19:11:44 -0800172 VersionedValue value = VersionedValue.copy(table.get(request.key()));
Madan Jampani12390c12014-11-12 00:35:56 -0800173 results.add(new ReadResult(ReadStatus.OK, request.tableName(), request.key(), value));
Madan Jampani08822c42014-11-04 17:17:46 -0800174 }
175 return results;
176 }
177
Yuta HIGUCHI841c0b62014-11-13 20:27:14 -0800178 @Query
179 public Map<String, VersionedValue> getAll(String tableName) {
180 return ImmutableMap.copyOf(state.getTable(tableName));
181 }
182
183
Yuta HIGUCHIfd0db482014-11-14 16:03:26 -0800184 WriteStatus checkIfApplicable(WriteRequest request, VersionedValue value) {
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800185
186 switch (request.type()) {
187 case PUT:
Madan Jampani12390c12014-11-12 00:35:56 -0800188 return WriteStatus.OK;
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800189
190 case PUT_IF_ABSENT:
191 if (value == null) {
Madan Jampani12390c12014-11-12 00:35:56 -0800192 return WriteStatus.OK;
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800193 }
Madan Jampani12390c12014-11-12 00:35:56 -0800194 return WriteStatus.PRECONDITION_VIOLATION;
Yuta HIGUCHIfd0db482014-11-14 16:03:26 -0800195
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800196 case PUT_IF_VALUE:
197 case REMOVE_IF_VALUE:
198 if (value != null && Arrays.equals(value.value(), request.oldValue())) {
Madan Jampani12390c12014-11-12 00:35:56 -0800199 return WriteStatus.OK;
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800200 }
Madan Jampani12390c12014-11-12 00:35:56 -0800201 return WriteStatus.PRECONDITION_VIOLATION;
Yuta HIGUCHIfd0db482014-11-14 16:03:26 -0800202
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800203 case PUT_IF_VERSION:
204 case REMOVE_IF_VERSION:
205 if (value != null && request.previousVersion() == value.version()) {
Madan Jampani12390c12014-11-12 00:35:56 -0800206 return WriteStatus.OK;
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800207 }
Madan Jampani12390c12014-11-12 00:35:56 -0800208 return WriteStatus.PRECONDITION_VIOLATION;
Yuta HIGUCHIfd0db482014-11-14 16:03:26 -0800209
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800210 case REMOVE:
Madan Jampani12390c12014-11-12 00:35:56 -0800211 return WriteStatus.OK;
Yuta HIGUCHIfd0db482014-11-14 16:03:26 -0800212
Yuta HIGUCHIc6b8f612014-11-06 19:04:13 -0800213 default:
214 break;
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800215 }
216 log.error("Should never reach here {}", request);
Madan Jampani12390c12014-11-12 00:35:56 -0800217 return WriteStatus.ABORTED;
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800218 }
219
Madan Jampani08822c42014-11-04 17:17:46 -0800220 @Command
Madan Jampani12390c12014-11-12 00:35:56 -0800221 public List<WriteResult> write(BatchWriteRequest batchRequest) {
Yuta HIGUCHIbddc81c42014-11-05 18:53:09 -0800222
223 // applicability check
Madan Jampani08822c42014-11-04 17:17:46 -0800224 boolean abort = false;
Yuta HIGUCHIfd0db482014-11-14 16:03:26 -0800225 List<WriteResult> results = new ArrayList<>(batchRequest.batchSize());
226
Madan Jampani12390c12014-11-12 00:35:56 -0800227 for (WriteRequest request : batchRequest.getAsList()) {
Madan Jampanidef2c652014-11-12 13:50:10 -0800228 Map<String, VersionedValue> table = state.getTable(request.tableName());
Madan Jampani08822c42014-11-04 17:17:46 -0800229 if (table == null) {
Yuta HIGUCHIfd0db482014-11-14 16:03:26 -0800230 results.add(new WriteResult(WriteStatus.NO_SUCH_TABLE, null));
Madan Jampani08822c42014-11-04 17:17:46 -0800231 abort = true;
232 continue;
233 }
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800234 final VersionedValue value = table.get(request.key());
Madan Jampani12390c12014-11-12 00:35:56 -0800235 WriteStatus result = checkIfApplicable(request, value);
Yuta HIGUCHIfd0db482014-11-14 16:03:26 -0800236 results.add(new WriteResult(result, value));
Madan Jampani12390c12014-11-12 00:35:56 -0800237 if (result != WriteStatus.OK) {
Madan Jampani08822c42014-11-04 17:17:46 -0800238 abort = true;
Madan Jampani08822c42014-11-04 17:17:46 -0800239 }
Madan Jampani08822c42014-11-04 17:17:46 -0800240 }
241
Madan Jampani08822c42014-11-04 17:17:46 -0800242 if (abort) {
Yuta HIGUCHIfd0db482014-11-14 16:03:26 -0800243 for (int i = 0; i < results.size(); ++i) {
244 if (results.get(i).status() == WriteStatus.OK) {
245 results.set(i, new WriteResult(WriteStatus.ABORTED, null));
Madan Jampani08822c42014-11-04 17:17:46 -0800246 }
247 }
248 return results;
249 }
250
Madan Jampani12390c12014-11-12 00:35:56 -0800251 List<TableModificationEvent> tableModificationEvents = Lists.newLinkedList();
252
Yuta HIGUCHIbddc81c42014-11-05 18:53:09 -0800253 // apply changes
Madan Jampani12390c12014-11-12 00:35:56 -0800254 for (WriteRequest request : batchRequest.getAsList()) {
Madan Jampanidef2c652014-11-12 13:50:10 -0800255 Map<String, VersionedValue> table = state.getTable(request.tableName());
Madan Jampani12390c12014-11-12 00:35:56 -0800256
257 TableModificationEvent tableModificationEvent = null;
Yuta HIGUCHIbddc81c42014-11-05 18:53:09 -0800258 // FIXME: If this method could be called by multiple thread,
259 // synchronization scope is wrong.
260 // Whole function including applicability check needs to be protected.
261 // Confirm copycat's thread safety requirement for StateMachine
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800262 // TODO: If we need isolation, we need to block reads also
Madan Jampani08822c42014-11-04 17:17:46 -0800263 synchronized (table) {
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800264 switch (request.type()) {
265 case PUT:
266 case PUT_IF_ABSENT:
267 case PUT_IF_VALUE:
268 case PUT_IF_VERSION:
269 VersionedValue newValue = new VersionedValue(request.newValue(), state.nextVersion());
270 VersionedValue previousValue = table.put(request.key(), newValue);
Madan Jampani12390c12014-11-12 00:35:56 -0800271 WriteResult putResult = new WriteResult(WriteStatus.OK, previousValue);
272 results.add(putResult);
273 tableModificationEvent = (previousValue == null) ?
Madan Jampani9b37d572014-11-12 11:53:24 -0800274 TableModificationEvent.rowAdded(request.tableName(), request.key(), newValue) :
275 TableModificationEvent.rowUpdated(request.tableName(), request.key(), newValue);
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800276 break;
277
278 case REMOVE:
279 case REMOVE_IF_VALUE:
280 case REMOVE_IF_VERSION:
281 VersionedValue removedValue = table.remove(request.key());
Madan Jampani12390c12014-11-12 00:35:56 -0800282 WriteResult removeResult = new WriteResult(WriteStatus.OK, removedValue);
283 results.add(removeResult);
284 if (removedValue != null) {
285 tableModificationEvent =
Madan Jampani9b37d572014-11-12 11:53:24 -0800286 TableModificationEvent.rowDeleted(request.tableName(), request.key(), removedValue);
Madan Jampani12390c12014-11-12 00:35:56 -0800287 }
Yuta HIGUCHI361664e2014-11-06 17:28:47 -0800288 break;
289
290 default:
291 log.error("Invalid WriteRequest type {}", request.type());
292 break;
293 }
Madan Jampani08822c42014-11-04 17:17:46 -0800294 }
Madan Jampani12390c12014-11-12 00:35:56 -0800295
296 if (tableModificationEvent != null) {
297 tableModificationEvents.add(tableModificationEvent);
298 }
Madan Jampani08822c42014-11-04 17:17:46 -0800299 }
Madan Jampani12390c12014-11-12 00:35:56 -0800300
301 // notify listeners of table mod events.
Madan Jampania88d1f52014-11-14 16:45:24 -0800302
303 updatesExecutor.submit(new Runnable() {
304 @Override
305 public void run() {
306 for (DatabaseUpdateEventListener listener : listeners) {
307 for (TableModificationEvent tableModificationEvent : tableModificationEvents) {
308 log.trace("Publishing table modification event: {}", tableModificationEvent);
309 listener.tableModified(tableModificationEvent);
310 }
311 }
Madan Jampani12390c12014-11-12 00:35:56 -0800312 }
Madan Jampania88d1f52014-11-14 16:45:24 -0800313 });
Madan Jampani12390c12014-11-12 00:35:56 -0800314
Madan Jampani08822c42014-11-04 17:17:46 -0800315 return results;
316 }
317
Madan Jampanidef2c652014-11-12 13:50:10 -0800318 public static class State {
Madan Jampani08822c42014-11-04 17:17:46 -0800319
Madan Jampanidef2c652014-11-12 13:50:10 -0800320 private final Map<String, TableMetadata> tableMetadata = Maps.newHashMap();
321 private final Map<String, Map<String, VersionedValue>> tableData = Maps.newHashMap();
Madan Jampani08822c42014-11-04 17:17:46 -0800322 private long versionCounter = 1;
323
Yuta HIGUCHI4490a732014-11-18 20:20:30 -0800324 Map<String, VersionedValue> getTable(String tableName) {
Madan Jampanidef2c652014-11-12 13:50:10 -0800325 return tableData.get(tableName);
326 }
327
328 void createTable(TableMetadata metadata) {
329 tableMetadata.put(metadata.tableName, metadata);
330 tableData.put(metadata.tableName, Maps.newHashMap());
331 }
332
333 TableMetadata getTableMetadata(String tableName) {
334 return tableMetadata.get(tableName);
Madan Jampani08822c42014-11-04 17:17:46 -0800335 }
336
337 long nextVersion() {
338 return versionCounter++;
339 }
Madan Jampanidef2c652014-11-12 13:50:10 -0800340
341 Set<String> getTableNames() {
342 return ImmutableSet.copyOf(tableMetadata.keySet());
343 }
344
345
346 boolean removeTable(String tableName) {
347 if (!tableMetadata.containsKey(tableName)) {
348 return false;
349 }
350 tableMetadata.remove(tableName);
351 tableData.remove(tableName);
352 return true;
353 }
354
355 void removeAllTables() {
356 tableMetadata.clear();
357 tableData.clear();
358 }
359 }
360
361 public static class TableMetadata {
362 private final String tableName;
363 private final boolean expireOldEntries;
364 private final int ttlMillis;
365
366 public TableMetadata(String tableName) {
367 this.tableName = tableName;
368 this.expireOldEntries = false;
369 this.ttlMillis = Integer.MAX_VALUE;
370
371 }
372
373 public TableMetadata(String tableName, int ttlMillis) {
374 this.tableName = tableName;
375 this.expireOldEntries = true;
376 this.ttlMillis = ttlMillis;
377 }
378
379 public String tableName() {
380 return tableName;
381 }
382
383 public boolean expireOldEntries() {
384 return expireOldEntries;
385 }
386
387 public int ttlMillis() {
388 return ttlMillis;
389 }
Madan Jampanif5d263b2014-11-13 10:04:40 -0800390
391 @Override
392 public String toString() {
393 return MoreObjects.toStringHelper(getClass())
394 .add("tableName", tableName)
395 .add("expireOldEntries", expireOldEntries)
396 .add("ttlMillis", ttlMillis)
397 .toString();
398 }
Madan Jampani08822c42014-11-04 17:17:46 -0800399 }
400
401 @Override
402 public byte[] takeSnapshot() {
403 try {
Yuta HIGUCHIa7680a32014-11-06 22:17:37 -0800404 if (compressSnapshot) {
405 byte[] input = SERIALIZER.encode(state);
406 ByteArrayOutputStream comp = new ByteArrayOutputStream(input.length);
407 DeflaterOutputStream compressor = new DeflaterOutputStream(comp);
408 compressor.write(input, 0, input.length);
409 compressor.close();
410 return comp.toByteArray();
411 } else {
412 return SERIALIZER.encode(state);
413 }
Madan Jampani08822c42014-11-04 17:17:46 -0800414 } catch (Exception e) {
Madan Jampani778f7ad2014-11-05 22:46:15 -0800415 log.error("Failed to take snapshot", e);
416 throw new SnapshotException(e);
Madan Jampani08822c42014-11-04 17:17:46 -0800417 }
418 }
419
420 @Override
421 public void installSnapshot(byte[] data) {
422 try {
Yuta HIGUCHIa7680a32014-11-06 22:17:37 -0800423 if (compressSnapshot) {
424 ByteArrayInputStream in = new ByteArrayInputStream(data);
425 InflaterInputStream decompressor = new InflaterInputStream(in);
426 ByteStreams.toByteArray(decompressor);
427 this.state = SERIALIZER.decode(ByteStreams.toByteArray(decompressor));
428 } else {
429 this.state = SERIALIZER.decode(data);
430 }
Madan Jampanidef2c652014-11-12 13:50:10 -0800431
Madan Jampania88d1f52014-11-14 16:45:24 -0800432 updatesExecutor.submit(new Runnable() {
433 @Override
434 public void run() {
435 for (DatabaseUpdateEventListener listener : listeners) {
436 listener.snapshotInstalled(state);
437 }
438 }
439 });
440
Madan Jampani08822c42014-11-04 17:17:46 -0800441 } catch (Exception e) {
Madan Jampani778f7ad2014-11-05 22:46:15 -0800442 log.error("Failed to install from snapshot", e);
443 throw new SnapshotException(e);
Madan Jampani08822c42014-11-04 17:17:46 -0800444 }
445 }
Madan Jampani12390c12014-11-12 00:35:56 -0800446
Madan Jampanidef2c652014-11-12 13:50:10 -0800447 /**
448 * Adds specified DatabaseUpdateEventListener.
449 * @param listener listener to add
450 */
Madan Jampani12390c12014-11-12 00:35:56 -0800451 public void addEventListener(DatabaseUpdateEventListener listener) {
452 listeners.add(listener);
453 }
Madan Jampanidef2c652014-11-12 13:50:10 -0800454
455 /**
456 * Removes specified DatabaseUpdateEventListener.
457 * @param listener listener to remove
458 */
459 public void removeEventListener(DatabaseUpdateEventListener listener) {
460 listeners.remove(listener);
461 }
Madan Jampani08822c42014-11-04 17:17:46 -0800462}