Jonathan Hart
Committed by Ray Milkey

Add persistence option to ECMap

Work towards ONOS-1337

Change-Id: I24e6a42e2f8856b363e79786829c51344797b81b
...@@ -166,6 +166,16 @@ public interface EventuallyConsistentMapBuilder<K, V> { ...@@ -166,6 +166,16 @@ public interface EventuallyConsistentMapBuilder<K, V> {
166 public EventuallyConsistentMapBuilder<K, V> withFasterConvergence(); 166 public EventuallyConsistentMapBuilder<K, V> withFasterConvergence();
167 167
168 /** 168 /**
169 + * Configure the map to persist data to disk.
170 + * <p>
171 + * The default behavior is no persistence
172 + * </p>
173 + *
174 + * @return this EventuallyConsistentMapBuilder
175 + */
176 + public EventuallyConsistentMapBuilder<K, V> withPersistence();
177 +
178 + /**
169 * Builds an eventually consistent map based on the configuration options 179 * Builds an eventually consistent map based on the configuration options
170 * supplied to this builder. 180 * supplied to this builder.
171 * 181 *
......
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
65 <dependency> 65 <dependency>
66 <groupId>org.mapdb</groupId> 66 <groupId>org.mapdb</groupId>
67 <artifactId>mapdb</artifactId> 67 <artifactId>mapdb</artifactId>
68 - <version>1.0.6</version> 68 + <version>1.0.7</version>
69 </dependency> 69 </dependency>
70 70
71 <dependency> 71 <dependency>
......
...@@ -51,6 +51,7 @@ public class EventuallyConsistentMapBuilderImpl<K, V> ...@@ -51,6 +51,7 @@ public class EventuallyConsistentMapBuilderImpl<K, V>
51 private long antiEntropyPeriod = 5; 51 private long antiEntropyPeriod = 5;
52 private TimeUnit antiEntropyTimeUnit = TimeUnit.SECONDS; 52 private TimeUnit antiEntropyTimeUnit = TimeUnit.SECONDS;
53 private boolean convergeFaster = false; 53 private boolean convergeFaster = false;
54 + private boolean persistent = false;
54 55
55 /** 56 /**
56 * Creates a new eventually consistent map builder. 57 * Creates a new eventually consistent map builder.
...@@ -131,6 +132,12 @@ public class EventuallyConsistentMapBuilderImpl<K, V> ...@@ -131,6 +132,12 @@ public class EventuallyConsistentMapBuilderImpl<K, V>
131 } 132 }
132 133
133 @Override 134 @Override
135 + public EventuallyConsistentMapBuilder<K, V> withPersistence() {
136 + persistent = true;
137 + return this;
138 + }
139 +
140 + @Override
134 public EventuallyConsistentMap<K, V> build() { 141 public EventuallyConsistentMap<K, V> build() {
135 checkNotNull(name, "name is a mandatory parameter"); 142 checkNotNull(name, "name is a mandatory parameter");
136 checkNotNull(serializerBuilder, "serializerBuilder is a mandatory parameter"); 143 checkNotNull(serializerBuilder, "serializerBuilder is a mandatory parameter");
...@@ -148,6 +155,7 @@ public class EventuallyConsistentMapBuilderImpl<K, V> ...@@ -148,6 +155,7 @@ public class EventuallyConsistentMapBuilderImpl<K, V>
148 tombstonesDisabled, 155 tombstonesDisabled,
149 antiEntropyPeriod, 156 antiEntropyPeriod,
150 antiEntropyTimeUnit, 157 antiEntropyTimeUnit,
151 - convergeFaster); 158 + convergeFaster,
159 + persistent);
152 } 160 }
153 } 161 }
......
...@@ -93,7 +93,6 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -93,7 +93,6 @@ public class EventuallyConsistentMapImpl<K, V>
93 = new CopyOnWriteArraySet<>(); 93 = new CopyOnWriteArraySet<>();
94 94
95 private final ExecutorService executor; 95 private final ExecutorService executor;
96 -
97 private final ScheduledExecutorService backgroundExecutor; 96 private final ScheduledExecutorService backgroundExecutor;
98 private final BiFunction<K, V, Collection<NodeId>> peerUpdateFunction; 97 private final BiFunction<K, V, Collection<NodeId>> peerUpdateFunction;
99 98
...@@ -116,6 +115,9 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -116,6 +115,9 @@ public class EventuallyConsistentMapImpl<K, V>
116 private static final int LOAD_WINDOW = 2; 115 private static final int LOAD_WINDOW = 2;
117 private SlidingWindowCounter counter = new SlidingWindowCounter(WINDOW_SIZE); 116 private SlidingWindowCounter counter = new SlidingWindowCounter(WINDOW_SIZE);
118 117
118 + private final boolean persistent;
119 + private final PersistentStore<K, V> persistentStore;
120 +
119 /** 121 /**
120 * Creates a new eventually consistent map shared amongst multiple instances. 122 * Creates a new eventually consistent map shared amongst multiple instances.
121 * <p> 123 * <p>
...@@ -140,9 +142,9 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -140,9 +142,9 @@ public class EventuallyConsistentMapImpl<K, V>
140 * @param tombstonesDisabled true if this map should not maintain 142 * @param tombstonesDisabled true if this map should not maintain
141 * tombstones 143 * tombstones
142 * @param antiEntropyPeriod period that the anti-entropy task should run 144 * @param antiEntropyPeriod period that the anti-entropy task should run
143 - * in seconds 145 + * @param antiEntropyTimeUnit time unit for anti-entropy period
144 - * @param antiEntropyTimeUnit time unit for anti-entropy task scheduling
145 * @param convergeFaster make anti-entropy try to converge faster 146 * @param convergeFaster make anti-entropy try to converge faster
147 + * @param persistent persist data to disk
146 */ 148 */
147 EventuallyConsistentMapImpl(String mapName, 149 EventuallyConsistentMapImpl(String mapName,
148 ClusterService clusterService, 150 ClusterService clusterService,
...@@ -156,7 +158,8 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -156,7 +158,8 @@ public class EventuallyConsistentMapImpl<K, V>
156 boolean tombstonesDisabled, 158 boolean tombstonesDisabled,
157 long antiEntropyPeriod, 159 long antiEntropyPeriod,
158 TimeUnit antiEntropyTimeUnit, 160 TimeUnit antiEntropyTimeUnit,
159 - boolean convergeFaster) { 161 + boolean convergeFaster,
162 + boolean persistent) {
160 items = new ConcurrentHashMap<>(); 163 items = new ConcurrentHashMap<>();
161 removedItems = new ConcurrentHashMap<>(); 164 removedItems = new ConcurrentHashMap<>();
162 senderPending = Maps.newConcurrentMap(); 165 senderPending = Maps.newConcurrentMap();
...@@ -195,6 +198,21 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -195,6 +198,21 @@ public class EventuallyConsistentMapImpl<K, V>
195 newFixedThreadPool(8, groupedThreads("onos/ecm", mapName + "-publish-%d")); 198 newFixedThreadPool(8, groupedThreads("onos/ecm", mapName + "-publish-%d"));
196 } 199 }
197 200
201 + this.persistent = persistent;
202 +
203 + if (this.persistent) {
204 + String dataDirectory = System.getProperty("karaf.data", "./data");
205 + String filename = dataDirectory + "/" + "mapdb-ecm-" + mapName;
206 +
207 + ExecutorService dbExecutor =
208 + newFixedThreadPool(1, groupedThreads("onos/ecm", mapName + "-dbwriter"));
209 +
210 + persistentStore = new MapDbPersistentStore<>(filename, dbExecutor, serializer);
211 + persistentStore.readInto(items, removedItems);
212 + } else {
213 + this.persistentStore = null;
214 + }
215 +
198 if (backgroundExecutor != null) { 216 if (backgroundExecutor != null) {
199 this.backgroundExecutor = backgroundExecutor; 217 this.backgroundExecutor = backgroundExecutor;
200 } else { 218 } else {
...@@ -232,6 +250,7 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -232,6 +250,7 @@ public class EventuallyConsistentMapImpl<K, V>
232 .register(ArrayList.class) 250 .register(ArrayList.class)
233 .register(AntiEntropyAdvertisement.class) 251 .register(AntiEntropyAdvertisement.class)
234 .register(HashMap.class) 252 .register(HashMap.class)
253 + .register(Timestamped.class)
235 .build(); 254 .build();
236 } 255 }
237 }; 256 };
...@@ -321,6 +340,11 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -321,6 +340,11 @@ public class EventuallyConsistentMapImpl<K, V>
321 if (success && removed != null) { 340 if (success && removed != null) {
322 removedItems.remove(key, removed); 341 removedItems.remove(key, removed);
323 } 342 }
343 +
344 + if (success && persistent) {
345 + persistentStore.put(key, value, timestamp);
346 + }
347 +
324 return success; 348 return success;
325 } 349 }
326 350
...@@ -357,24 +381,29 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -357,24 +381,29 @@ public class EventuallyConsistentMapImpl<K, V>
357 // remove from items map 381 // remove from items map
358 return null; 382 return null;
359 } 383 }
360 - }); 384 + });
361 385
362 if (updated.isFalse()) { 386 if (updated.isFalse()) {
363 return false; 387 return false;
364 } 388 }
365 389
390 + boolean updatedTombstone = false;
391 +
366 if (!tombstonesDisabled) { 392 if (!tombstonesDisabled) {
367 Timestamp removedTimestamp = removedItems.get(key); 393 Timestamp removedTimestamp = removedItems.get(key);
368 if (removedTimestamp == null) { 394 if (removedTimestamp == null) {
369 - return removedItems.putIfAbsent(key, timestamp) == null; 395 + //Timestamp removed = removedItems.putIfAbsent(key, timestamp);
396 + updatedTombstone = (removedItems.putIfAbsent(key, timestamp) == null);
370 } else if (timestamp.isNewerThan(removedTimestamp)) { 397 } else if (timestamp.isNewerThan(removedTimestamp)) {
371 - return removedItems.replace(key, removedTimestamp, timestamp); 398 + updatedTombstone = removedItems.replace(key, removedTimestamp, timestamp);
372 - } else {
373 - return false;
374 } 399 }
375 } 400 }
376 401
377 - return updated.booleanValue(); 402 + if (updated.booleanValue() && persistent) {
403 + persistentStore.remove(key, timestamp);
404 + }
405 +
406 + return (!tombstonesDisabled && updatedTombstone) || updated.booleanValue();
378 } 407 }
379 408
380 @Override 409 @Override
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.store.ecmap;
18 +
19 +import org.apache.commons.lang3.mutable.MutableBoolean;
20 +import org.mapdb.DB;
21 +import org.mapdb.DBMaker;
22 +import org.mapdb.Hasher;
23 +import org.mapdb.Serializer;
24 +import org.onosproject.store.Timestamp;
25 +import org.onosproject.store.impl.Timestamped;
26 +import org.onosproject.store.serializers.KryoSerializer;
27 +
28 +import java.io.File;
29 +import java.util.Map;
30 +import java.util.concurrent.ExecutorService;
31 +
32 +import static com.google.common.base.Preconditions.checkNotNull;
33 +
34 +/**
35 + * MapDB based implementation of a persistent store.
36 + */
37 +class MapDbPersistentStore<K, V> implements PersistentStore<K, V> {
38 +
39 + private final ExecutorService executor;
40 + private final KryoSerializer serializer;
41 +
42 + private final DB database;
43 +
44 + private final Map<byte[], byte[]> items;
45 + private final Map<byte[], byte[]> tombstones;
46 +
47 + /**
48 + * Creates a new MapDB based persistent store.
49 + *
50 + * @param filename filename of the database on disk
51 + * @param executor executor to use for tasks that write to the disk
52 + * @param serializer serializer for keys and values
53 + */
54 + MapDbPersistentStore(String filename, ExecutorService executor,
55 + KryoSerializer serializer) {
56 + this.executor = checkNotNull(executor);
57 + this.serializer = checkNotNull(serializer);
58 +
59 + File databaseFile = new File(filename);
60 +
61 + database = DBMaker.newFileDB(databaseFile).make();
62 +
63 + items = database.createHashMap("items")
64 + .keySerializer(Serializer.BYTE_ARRAY)
65 + .valueSerializer(Serializer.BYTE_ARRAY)
66 + .hasher(Hasher.BYTE_ARRAY)
67 + .makeOrGet();
68 +
69 + tombstones = database.createHashMap("tombstones")
70 + .keySerializer(Serializer.BYTE_ARRAY)
71 + .valueSerializer(Serializer.BYTE_ARRAY)
72 + .hasher(Hasher.BYTE_ARRAY)
73 + .makeOrGet();
74 + }
75 +
76 + @Override
77 + public void readInto(Map<K, Timestamped<V>> items, Map<K, Timestamp> tombstones) {
78 + this.items.forEach((keyBytes, valueBytes) ->
79 + items.put(serializer.decode(keyBytes),
80 + serializer.decode(valueBytes)));
81 +
82 + this.tombstones.forEach((keyBytes, valueBytes) ->
83 + tombstones.put(serializer.decode(keyBytes),
84 + serializer.decode(valueBytes)));
85 + }
86 +
87 + @Override
88 + public void put(K key, V value, Timestamp timestamp) {
89 + executor.submit(() -> putInternal(key, value, timestamp));
90 + }
91 +
92 + private void putInternal(K key, V value, Timestamp timestamp) {
93 + byte[] keyBytes = serializer.encode(key);
94 + byte[] removedBytes = tombstones.get(keyBytes);
95 +
96 + Timestamp removed = removedBytes == null ? null :
97 + serializer.decode(removedBytes);
98 + if (removed != null && removed.isNewerThan(timestamp)) {
99 + return;
100 + }
101 +
102 + final MutableBoolean updated = new MutableBoolean(false);
103 +
104 + items.compute(keyBytes, (k, existingBytes) -> {
105 + Timestamped<V> existing = existingBytes == null ? null :
106 + serializer.decode(existingBytes);
107 + if (existing != null && existing.isNewerThan(timestamp)) {
108 + updated.setFalse();
109 + return existingBytes;
110 + } else {
111 + updated.setTrue();
112 + return serializer.encode(new Timestamped<>(value, timestamp));
113 + }
114 + });
115 +
116 + boolean success = updated.booleanValue();
117 +
118 + if (success && removed != null) {
119 + tombstones.remove(keyBytes, removedBytes);
120 + }
121 +
122 + database.commit();
123 + }
124 +
125 + @Override
126 + public void remove(K key, Timestamp timestamp) {
127 + executor.submit(() -> removeInternal(key, timestamp));
128 + }
129 +
130 + private void removeInternal(K key, Timestamp timestamp) {
131 + byte[] keyBytes = serializer.encode(key);
132 +
133 + final MutableBoolean updated = new MutableBoolean(false);
134 +
135 + items.compute(keyBytes, (k, existingBytes) -> {
136 + Timestamp existing = existingBytes == null ? null :
137 + serializer.decode(existingBytes);
138 + if (existing != null && existing.isNewerThan(timestamp)) {
139 + updated.setFalse();
140 + return existingBytes;
141 + } else {
142 + updated.setTrue();
143 + // remove from items map
144 + return null;
145 + }
146 + });
147 +
148 + if (!updated.booleanValue()) {
149 + return;
150 + }
151 +
152 + byte[] timestampBytes = serializer.encode(timestamp);
153 + byte[] removedBytes = tombstones.get(keyBytes);
154 +
155 + Timestamp removedTimestamp = removedBytes == null ? null :
156 + serializer.decode(removedBytes);
157 + if (removedTimestamp == null) {
158 + tombstones.putIfAbsent(keyBytes, timestampBytes);
159 + } else if (timestamp.isNewerThan(removedTimestamp)) {
160 + tombstones.replace(keyBytes, removedBytes, timestampBytes);
161 + }
162 +
163 + database.commit();
164 + }
165 +
166 +}
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.store.ecmap;
18 +
19 +import org.onosproject.store.Timestamp;
20 +import org.onosproject.store.impl.Timestamped;
21 +
22 +import java.util.Map;
23 +
24 +/**
25 + * A persistent store for an eventually consistent map.
26 + */
27 +interface PersistentStore<K, V> {
28 +
29 + /**
30 + * Read the contents of the disk into the given maps.
31 + *
32 + * @param items items map
33 + * @param tombstones tombstones map
34 + */
35 + void readInto(Map<K, Timestamped<V>> items, Map<K, Timestamp> tombstones);
36 +
37 + /**
38 + * Puts a new key,value pair into the map on disk.
39 + *
40 + * @param key the key
41 + * @param value the value
42 + * @param timestamp the timestamp of the update
43 + */
44 + void put(K key, V value, Timestamp timestamp);
45 +
46 + /**
47 + * Removes a key from the map on disk.
48 + *
49 + * @param key the key
50 + * @param timestamp the timestamp of the update
51 + */
52 + void remove(K key, Timestamp timestamp);
53 +}
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
64 <bundle>mvn:com.typesafe/config/1.2.1</bundle> 64 <bundle>mvn:com.typesafe/config/1.2.1</bundle>
65 <bundle>mvn:org.onosproject/onlab-thirdparty/@ONOS-VERSION</bundle> 65 <bundle>mvn:org.onosproject/onlab-thirdparty/@ONOS-VERSION</bundle>
66 66
67 - <bundle>mvn:org.mapdb/mapdb/1.0.6</bundle> 67 + <bundle>mvn:org.mapdb/mapdb/1.0.7</bundle>
68 </feature> 68 </feature>
69 69
70 <feature name="onos-thirdparty-web" version="@FEATURE-VERSION" 70 <feature name="onos-thirdparty-web" version="@FEATURE-VERSION"
......