Initial implementation of EventuallyConsistentMap.
The map uses the gossip schemes to replicate data between instances. It seems to work for basic add and remove use cases right now, no anti-entropy yet. ONOS-844. Change-Id: I7d05a7b532e40c95ab14e2c8911f18514bd0a8ca
Showing
6 changed files
with
930 additions
and
0 deletions
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 | +package org.onosproject.store.impl; | ||
17 | + | ||
18 | +import org.onosproject.store.Timestamp; | ||
19 | + | ||
20 | +/** | ||
21 | + * Clock service that can generate timestamps per object. | ||
22 | + */ | ||
23 | +public interface ClockService<T> { | ||
24 | + | ||
25 | + /** | ||
26 | + * Gets a new timestamp for the given object. | ||
27 | + * | ||
28 | + * @param object Object to get a timestamp for | ||
29 | + * @return the new timestamp | ||
30 | + */ | ||
31 | + public Timestamp getTimestamp(T object); | ||
32 | +} |
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 | +package org.onosproject.store.impl; | ||
17 | + | ||
18 | +import java.util.Collection; | ||
19 | +import java.util.Map; | ||
20 | +import java.util.Set; | ||
21 | + | ||
22 | +/** | ||
23 | + * A distributed, eventually consistent map. | ||
24 | + * | ||
25 | + * This map does not offer read after writes consistency. Operations are | ||
26 | + * serialized via the timestamps issued by the clock service. If two updates | ||
27 | + * are in conflict, the update with the more recent timestamp will endure. | ||
28 | + * | ||
29 | + * The interface is mostly similar to {@link java.util.Map} with some minor | ||
30 | + * semantic changes and the addition of a listener framework (because the map | ||
31 | + * can be mutated by clients on other instances, not only through the local Java | ||
32 | + * API). | ||
33 | + * | ||
34 | + * Clients are expected to register an | ||
35 | + * {@link org.onosproject.store.impl.EventuallyConsistentMapListener} if they | ||
36 | + * are interested in receiving notifications of update to the map. | ||
37 | + */ | ||
38 | +public interface EventuallyConsistentMap<K, V> { | ||
39 | + | ||
40 | + /** | ||
41 | + * Returns the number of key-value mappings in this map. | ||
42 | + * | ||
43 | + * @return number of key-value mappings | ||
44 | + */ | ||
45 | + public int size(); | ||
46 | + | ||
47 | + /** | ||
48 | + * Returns true if this map is empty. | ||
49 | + * | ||
50 | + * @return true if this map is empty, otherwise false | ||
51 | + */ | ||
52 | + public boolean isEmpty(); | ||
53 | + | ||
54 | + /** | ||
55 | + * Returns true if the map contains a mapping for the specified key. | ||
56 | + * | ||
57 | + * @param key the key to check if this map contains | ||
58 | + * @return true if this map has a mapping for the key, otherwise false | ||
59 | + */ | ||
60 | + public boolean containsKey(K key); | ||
61 | + | ||
62 | + /** | ||
63 | + * Returns true if the map contains a mapping from any key to the specified | ||
64 | + * value. | ||
65 | + * | ||
66 | + * @param value the value to check if this map has a mapping for | ||
67 | + * @return true if this map has a mapping to this value, otherwise false | ||
68 | + */ | ||
69 | + public boolean containsValue(V value); | ||
70 | + | ||
71 | + /** | ||
72 | + * Returns the value mapped to the specified key. | ||
73 | + * | ||
74 | + * @param key the key to look up in this map | ||
75 | + * @return the value mapped to the key, or null if no mapping is found | ||
76 | + */ | ||
77 | + public V get(K key); | ||
78 | + | ||
79 | + /** | ||
80 | + * Associates the specified value to the specified key in this map. | ||
81 | + * <p> | ||
82 | + * Note: this differs from the specification of {@link java.util.Map} | ||
83 | + * because it does not return the previous value associated with the key. | ||
84 | + * Clients are expected to register an | ||
85 | + * {@link org.onosproject.store.impl.EventuallyConsistentMapListener} if | ||
86 | + * they are interested in receiving notification of updates to the map. | ||
87 | + * </p> | ||
88 | + * | ||
89 | + * @param key the key to add a mapping for in this map | ||
90 | + * @param value the value to associate with the key in this map | ||
91 | + */ | ||
92 | + public void put(K key, V value); | ||
93 | + | ||
94 | + /** | ||
95 | + * Removes the mapping associated with the specified key from the map. | ||
96 | + * <p> | ||
97 | + * Note: this differs from the specification of {@link java.util.Map} | ||
98 | + * because it does not return the previous value associated with the key. | ||
99 | + * Clients are expected to register an | ||
100 | + * {@link org.onosproject.store.impl.EventuallyConsistentMapListener} if | ||
101 | + * they are interested in receiving notification of updates to the map. | ||
102 | + * </p> | ||
103 | + * | ||
104 | + * @param key the key to remove the mapping for | ||
105 | + */ | ||
106 | + public void remove(K key); | ||
107 | + | ||
108 | + /** | ||
109 | + * Adds mappings for all key-value pairs in the specified map to this map. | ||
110 | + * <p> | ||
111 | + * This will be more efficient in communication than calling individual put | ||
112 | + * operations. | ||
113 | + * </p> | ||
114 | + * | ||
115 | + * @param m a map of values to add to this map | ||
116 | + */ | ||
117 | + public void putAll(Map<? extends K, ? extends V> m); | ||
118 | + | ||
119 | + /** | ||
120 | + * Removes all mappings from this map. | ||
121 | + */ | ||
122 | + public void clear(); | ||
123 | + | ||
124 | + /** | ||
125 | + * Returns a set of the keys in this map. Changes to the set are not | ||
126 | + * reflected back to the map. | ||
127 | + * | ||
128 | + * @return set of keys in the map | ||
129 | + */ | ||
130 | + public Set<K> keySet(); | ||
131 | + | ||
132 | + /** | ||
133 | + * Returns a collections of values in this map. Changes to the collection | ||
134 | + * are not reflected back to the map. | ||
135 | + * | ||
136 | + * @return collection of values in the map | ||
137 | + */ | ||
138 | + public Collection<V> values(); | ||
139 | + | ||
140 | + /** | ||
141 | + * Returns a set of mappings contained in this map. Changes to the set are | ||
142 | + * not reflected back to the map. | ||
143 | + * | ||
144 | + * @return set of key-value mappings in this map | ||
145 | + */ | ||
146 | + public Set<Map.Entry<K, V>> entrySet(); | ||
147 | + | ||
148 | + /** | ||
149 | + * Adds the specified listener to the map which will be notified whenever | ||
150 | + * the mappings in the map are changed. | ||
151 | + * | ||
152 | + * @param listener listener to register for events | ||
153 | + */ | ||
154 | + public void addListener(EventuallyConsistentMapListener listener); | ||
155 | + | ||
156 | + /** | ||
157 | + * Removes the specified listener from the map such that it will no longer | ||
158 | + * receive change notifications. | ||
159 | + * | ||
160 | + * @param listener listener to deregister for events | ||
161 | + */ | ||
162 | + public void removeListener(EventuallyConsistentMapListener listener); | ||
163 | + | ||
164 | + /** | ||
165 | + * Shuts down the map and breaks communication between different instances. | ||
166 | + * This allows the map objects to be cleaned up and garbage collected. | ||
167 | + * Calls to any methods on the map subsequent to calling destroy() will | ||
168 | + * throw a {@link java.lang.RuntimeException}. | ||
169 | + */ | ||
170 | + public void destroy(); | ||
171 | +} |
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMapEvent.java
0 → 100644
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 | +package org.onosproject.store.impl; | ||
17 | + | ||
18 | +/** | ||
19 | + * Event object signalling that the map was modified. | ||
20 | + */ | ||
21 | +public class EventuallyConsistentMapEvent<K, V> { | ||
22 | + | ||
23 | + public enum Type { | ||
24 | + PUT, | ||
25 | + REMOVE | ||
26 | + } | ||
27 | + | ||
28 | + private final Type type; | ||
29 | + private final K key; | ||
30 | + private final V value; | ||
31 | + | ||
32 | + /** | ||
33 | + * Creates a new event object. | ||
34 | + * | ||
35 | + * @param type the type of the event | ||
36 | + * @param key the key the event concerns | ||
37 | + * @param value the value related to the key, or null for remove events | ||
38 | + */ | ||
39 | + public EventuallyConsistentMapEvent(Type type, K key, V value) { | ||
40 | + this.type = type; | ||
41 | + this.key = key; | ||
42 | + this.value = value; | ||
43 | + } | ||
44 | + | ||
45 | + /** | ||
46 | + * Returns the type of the event. | ||
47 | + * | ||
48 | + * @return the type of the event | ||
49 | + */ | ||
50 | + public Type type() { | ||
51 | + return type; | ||
52 | + } | ||
53 | + | ||
54 | + /** | ||
55 | + * Returns the key this event concerns. | ||
56 | + * | ||
57 | + * @return the key | ||
58 | + */ | ||
59 | + public K key() { | ||
60 | + return key; | ||
61 | + } | ||
62 | + | ||
63 | + /** | ||
64 | + * Returns the value associated with this event. | ||
65 | + * | ||
66 | + * @return the value, or null if the event was REMOVE | ||
67 | + */ | ||
68 | + public V value() { | ||
69 | + return value; | ||
70 | + } | ||
71 | +} |
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMapImpl.java
0 → 100644
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 | +package org.onosproject.store.impl; | ||
17 | + | ||
18 | +import com.google.common.base.MoreObjects; | ||
19 | +import org.onlab.util.KryoNamespace; | ||
20 | +import org.onosproject.cluster.ClusterService; | ||
21 | +import org.onosproject.cluster.NodeId; | ||
22 | +import org.onosproject.store.Timestamp; | ||
23 | +import org.onosproject.store.cluster.messaging.ClusterCommunicationService; | ||
24 | +import org.onosproject.store.cluster.messaging.ClusterMessage; | ||
25 | +import org.onosproject.store.cluster.messaging.ClusterMessageHandler; | ||
26 | +import org.onosproject.store.cluster.messaging.MessageSubject; | ||
27 | +import org.onosproject.store.serializers.KryoSerializer; | ||
28 | +import org.slf4j.Logger; | ||
29 | +import org.slf4j.LoggerFactory; | ||
30 | + | ||
31 | +import java.io.IOException; | ||
32 | +import java.util.ArrayList; | ||
33 | +import java.util.Collection; | ||
34 | +import java.util.Collections; | ||
35 | +import java.util.List; | ||
36 | +import java.util.Map; | ||
37 | +import java.util.Set; | ||
38 | +import java.util.concurrent.ConcurrentHashMap; | ||
39 | +import java.util.concurrent.CopyOnWriteArraySet; | ||
40 | +import java.util.concurrent.ExecutorService; | ||
41 | +import java.util.concurrent.Executors; | ||
42 | +import java.util.concurrent.ScheduledExecutorService; | ||
43 | +import java.util.stream.Collectors; | ||
44 | + | ||
45 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
46 | +import static com.google.common.base.Preconditions.checkState; | ||
47 | +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; | ||
48 | +import static org.onlab.util.Tools.minPriority; | ||
49 | +import static org.onlab.util.Tools.namedThreads; | ||
50 | + | ||
51 | +/** | ||
52 | + * Distributed Map implementation which uses optimistic replication and gossip | ||
53 | + * based techniques to provide an eventually consistent data store. | ||
54 | + */ | ||
55 | +public class EventuallyConsistentMapImpl<K, V> | ||
56 | + implements EventuallyConsistentMap<K, V> { | ||
57 | + | ||
58 | + private static final Logger log = LoggerFactory.getLogger(EventuallyConsistentMapImpl.class); | ||
59 | + | ||
60 | + private final Map<K, Timestamped<V>> items; | ||
61 | + private final Map<K, Timestamp> removedItems; | ||
62 | + | ||
63 | + private final String mapName; | ||
64 | + private final ClusterService clusterService; | ||
65 | + private final ClusterCommunicationService clusterCommunicator; | ||
66 | + private final KryoSerializer serializer; | ||
67 | + | ||
68 | + private final ClockService<K> clockService; | ||
69 | + | ||
70 | + private final MessageSubject updateMessageSubject; | ||
71 | + private final MessageSubject removeMessageSubject; | ||
72 | + | ||
73 | + private final Set<EventuallyConsistentMapListener> listeners | ||
74 | + = new CopyOnWriteArraySet<>(); | ||
75 | + | ||
76 | + private final ExecutorService executor; | ||
77 | + | ||
78 | + private final ScheduledExecutorService backgroundExecutor; | ||
79 | + | ||
80 | + private volatile boolean destroyed = false; | ||
81 | + private static final String ERROR_DESTROYED = " is already destroyed"; | ||
82 | + | ||
83 | + // TODO: Make these anti-entropy params configurable | ||
84 | + private long initialDelaySec = 5; | ||
85 | + private long periodSec = 5; | ||
86 | + | ||
87 | + /** | ||
88 | + * Creates a new eventually consistent map shared amongst multiple instances. | ||
89 | + * | ||
90 | + * Each map is identified by a string map name. EventuallyConsistentMapImpl | ||
91 | + * objects in different JVMs that use the same map name will form a | ||
92 | + * distributed map across JVMs (provided the cluster service is aware of | ||
93 | + * both nodes). | ||
94 | + * | ||
95 | + * The client is expected to provide an | ||
96 | + * {@link org.onlab.util.KryoNamespace.Builder} with which all classes that | ||
97 | + * will be stored in this map have been registered (including referenced | ||
98 | + * classes). This serializer will be used to serialize both K and V for | ||
99 | + * inter-node notifications. | ||
100 | + * | ||
101 | + * The client must provide an {@link org.onosproject.store.impl.ClockService} | ||
102 | + * which can generate timestamps for a given key. The clock service is free | ||
103 | + * to generate timestamps however it wishes, however these timestamps will | ||
104 | + * be used to serialize updates to the map so they must be strict enough | ||
105 | + * to ensure updates are properly ordered for the use case (i.e. in some | ||
106 | + * cases wallclock time will suffice, whereas in other cases logical time | ||
107 | + * will be necessary). | ||
108 | + * | ||
109 | + * @param mapName a String identifier for the map. | ||
110 | + * @param clusterService the cluster service | ||
111 | + * @param clusterCommunicator the cluster communications service | ||
112 | + * @param serializerBuilder a Kryo namespace builder that can serialize | ||
113 | + * both K and V | ||
114 | + * @param clockService a clock service able to generate timestamps | ||
115 | + * for K | ||
116 | + */ | ||
117 | + public EventuallyConsistentMapImpl(String mapName, | ||
118 | + ClusterService clusterService, | ||
119 | + ClusterCommunicationService clusterCommunicator, | ||
120 | + KryoNamespace.Builder serializerBuilder, | ||
121 | + ClockService<K> clockService) { | ||
122 | + | ||
123 | + this.mapName = checkNotNull(mapName); | ||
124 | + this.clusterService = checkNotNull(clusterService); | ||
125 | + this.clusterCommunicator = checkNotNull(clusterCommunicator); | ||
126 | + | ||
127 | + serializer = createSerializer(checkNotNull(serializerBuilder)); | ||
128 | + | ||
129 | + this.clockService = checkNotNull(clockService); | ||
130 | + | ||
131 | + items = new ConcurrentHashMap<>(); | ||
132 | + removedItems = new ConcurrentHashMap<>(); | ||
133 | + | ||
134 | + executor = Executors | ||
135 | + .newCachedThreadPool(namedThreads("onos-ecm-" + mapName + "-fg-%d")); | ||
136 | + | ||
137 | + backgroundExecutor = | ||
138 | + newSingleThreadScheduledExecutor(minPriority( | ||
139 | + namedThreads("onos-ecm-" + mapName + "-bg-%d"))); | ||
140 | + | ||
141 | + updateMessageSubject = new MessageSubject("ecm-" + mapName + "-update"); | ||
142 | + clusterCommunicator.addSubscriber(updateMessageSubject, | ||
143 | + new InternalPutEventListener()); | ||
144 | + removeMessageSubject = new MessageSubject("ecm-" + mapName + "-remove"); | ||
145 | + clusterCommunicator.addSubscriber(removeMessageSubject, | ||
146 | + new InternalRemoveEventListener()); | ||
147 | + } | ||
148 | + | ||
149 | + private KryoSerializer createSerializer(KryoNamespace.Builder builder) { | ||
150 | + return new KryoSerializer() { | ||
151 | + @Override | ||
152 | + protected void setupKryoPool() { | ||
153 | + // Add the map's internal helper classes to the user-supplied serializer | ||
154 | + serializerPool = builder | ||
155 | + .register(WallClockTimestamp.class) | ||
156 | + .register(PutEntry.class) | ||
157 | + .register(ArrayList.class) | ||
158 | + .register(InternalPutEvent.class) | ||
159 | + .register(InternalRemoveEvent.class) | ||
160 | + .build(); | ||
161 | + | ||
162 | + // TODO anti-entropy classes | ||
163 | + } | ||
164 | + }; | ||
165 | + } | ||
166 | + | ||
167 | + @Override | ||
168 | + public int size() { | ||
169 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
170 | + return items.size(); | ||
171 | + } | ||
172 | + | ||
173 | + @Override | ||
174 | + public boolean isEmpty() { | ||
175 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
176 | + return items.isEmpty(); | ||
177 | + } | ||
178 | + | ||
179 | + @Override | ||
180 | + public boolean containsKey(K key) { | ||
181 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
182 | + return items.containsKey(key); | ||
183 | + } | ||
184 | + | ||
185 | + @Override | ||
186 | + public boolean containsValue(V value) { | ||
187 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
188 | + | ||
189 | + return items.values().stream() | ||
190 | + .anyMatch(timestamped -> timestamped.value().equals(value)); | ||
191 | + } | ||
192 | + | ||
193 | + @Override | ||
194 | + public V get(K key) { | ||
195 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
196 | + | ||
197 | + Timestamped<V> value = items.get(key); | ||
198 | + if (value != null) { | ||
199 | + return value.value(); | ||
200 | + } | ||
201 | + return null; | ||
202 | + } | ||
203 | + | ||
204 | + @Override | ||
205 | + public void put(K key, V value) { | ||
206 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
207 | + | ||
208 | + Timestamp timestamp = clockService.getTimestamp(key); | ||
209 | + if (putInternal(key, value, timestamp)) { | ||
210 | + notifyPeers(new InternalPutEvent<>(key, value, timestamp)); | ||
211 | + EventuallyConsistentMapEvent<K, V> externalEvent | ||
212 | + = new EventuallyConsistentMapEvent<>( | ||
213 | + EventuallyConsistentMapEvent.Type.PUT, key, value); | ||
214 | + notifyListeners(externalEvent); | ||
215 | + } | ||
216 | + } | ||
217 | + | ||
218 | + private boolean putInternal(K key, V value, Timestamp timestamp) { | ||
219 | + synchronized (this) { | ||
220 | + Timestamp removed = removedItems.get(key); | ||
221 | + if (removed != null && removed.compareTo(timestamp) > 0) { | ||
222 | + return false; | ||
223 | + } | ||
224 | + | ||
225 | + Timestamped<V> existing = items.get(key); | ||
226 | + if (existing != null && existing.isNewer(timestamp)) { | ||
227 | + return false; | ||
228 | + } else { | ||
229 | + items.put(key, new Timestamped<>(value, timestamp)); | ||
230 | + removedItems.remove(key); | ||
231 | + return true; | ||
232 | + } | ||
233 | + } | ||
234 | + } | ||
235 | + | ||
236 | + @Override | ||
237 | + public void remove(K key) { | ||
238 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
239 | + | ||
240 | + Timestamp timestamp = clockService.getTimestamp(key); | ||
241 | + if (removeInternal(key, timestamp)) { | ||
242 | + notifyPeers(new InternalRemoveEvent<>(key, timestamp)); | ||
243 | + EventuallyConsistentMapEvent<K, V> externalEvent | ||
244 | + = new EventuallyConsistentMapEvent<>( | ||
245 | + EventuallyConsistentMapEvent.Type.REMOVE, key, null); | ||
246 | + notifyListeners(externalEvent); | ||
247 | + } | ||
248 | + } | ||
249 | + | ||
250 | + private boolean removeInternal(K key, Timestamp timestamp) { | ||
251 | + synchronized (this) { | ||
252 | + if (items.get(key) != null && items.get(key).isNewer(timestamp)) { | ||
253 | + return false; | ||
254 | + } | ||
255 | + | ||
256 | + items.remove(key); | ||
257 | + removedItems.put(key, timestamp); | ||
258 | + return true; | ||
259 | + } | ||
260 | + } | ||
261 | + | ||
262 | + @Override | ||
263 | + public void putAll(Map<? extends K, ? extends V> m) { | ||
264 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
265 | + | ||
266 | + List<PutEntry<K, V>> updates = new ArrayList<>(m.size()); | ||
267 | + | ||
268 | + for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) { | ||
269 | + K key = entry.getKey(); | ||
270 | + V value = entry.getValue(); | ||
271 | + Timestamp timestamp = clockService.getTimestamp(entry.getKey()); | ||
272 | + | ||
273 | + if (putInternal(key, value, timestamp)) { | ||
274 | + updates.add(new PutEntry<>(key, value, timestamp)); | ||
275 | + } | ||
276 | + } | ||
277 | + | ||
278 | + notifyPeers(new InternalPutEvent<>(updates)); | ||
279 | + | ||
280 | + for (PutEntry<K, V> entry : updates) { | ||
281 | + EventuallyConsistentMapEvent<K, V> externalEvent = | ||
282 | + new EventuallyConsistentMapEvent<>( | ||
283 | + EventuallyConsistentMapEvent.Type.PUT, entry.key(), entry.value()); | ||
284 | + notifyListeners(externalEvent); | ||
285 | + } | ||
286 | + } | ||
287 | + | ||
288 | + @Override | ||
289 | + public void clear() { | ||
290 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
291 | + | ||
292 | + List<RemoveEntry<K>> removed = new ArrayList<>(items.size()); | ||
293 | + | ||
294 | + for (K key : items.keySet()) { | ||
295 | + Timestamp timestamp = clockService.getTimestamp(key); | ||
296 | + | ||
297 | + if (removeInternal(key, timestamp)) { | ||
298 | + removed.add(new RemoveEntry<>(key, timestamp)); | ||
299 | + } | ||
300 | + } | ||
301 | + | ||
302 | + notifyPeers(new InternalRemoveEvent<>(removed)); | ||
303 | + | ||
304 | + for (RemoveEntry<K> entry : removed) { | ||
305 | + EventuallyConsistentMapEvent<K, V> externalEvent = | ||
306 | + new EventuallyConsistentMapEvent<>( | ||
307 | + EventuallyConsistentMapEvent.Type.REMOVE, entry.key(), null); | ||
308 | + notifyListeners(externalEvent); | ||
309 | + } | ||
310 | + } | ||
311 | + | ||
312 | + @Override | ||
313 | + public Set<K> keySet() { | ||
314 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
315 | + | ||
316 | + return items.keySet(); | ||
317 | + } | ||
318 | + | ||
319 | + @Override | ||
320 | + public Collection<V> values() { | ||
321 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
322 | + | ||
323 | + return items.values().stream() | ||
324 | + .map(Timestamped::value) | ||
325 | + .collect(Collectors.toList()); | ||
326 | + } | ||
327 | + | ||
328 | + @Override | ||
329 | + public Set<Map.Entry<K, V>> entrySet() { | ||
330 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
331 | + | ||
332 | + return items.entrySet().stream() | ||
333 | + .map(e -> new Entry(e.getKey(), e.getValue().value())) | ||
334 | + .collect(Collectors.toSet()); | ||
335 | + } | ||
336 | + | ||
337 | + @Override | ||
338 | + public void addListener(EventuallyConsistentMapListener listener) { | ||
339 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
340 | + | ||
341 | + listeners.add(checkNotNull(listener)); | ||
342 | + } | ||
343 | + | ||
344 | + @Override | ||
345 | + public void removeListener(EventuallyConsistentMapListener listener) { | ||
346 | + checkState(destroyed, mapName + ERROR_DESTROYED); | ||
347 | + | ||
348 | + listeners.remove(checkNotNull(listener)); | ||
349 | + } | ||
350 | + | ||
351 | + @Override | ||
352 | + public void destroy() { | ||
353 | + destroyed = true; | ||
354 | + | ||
355 | + executor.shutdown(); | ||
356 | + backgroundExecutor.shutdown(); | ||
357 | + | ||
358 | + clusterCommunicator.removeSubscriber(updateMessageSubject); | ||
359 | + clusterCommunicator.removeSubscriber(removeMessageSubject); | ||
360 | + } | ||
361 | + | ||
362 | + private void notifyListeners(EventuallyConsistentMapEvent event) { | ||
363 | + for (EventuallyConsistentMapListener listener : listeners) { | ||
364 | + listener.event(event); | ||
365 | + } | ||
366 | + } | ||
367 | + | ||
368 | + private void notifyPeers(InternalPutEvent event) { | ||
369 | + try { | ||
370 | + log.debug("sending put {}", event); | ||
371 | + broadcastMessage(updateMessageSubject, event); | ||
372 | + } catch (IOException e) { | ||
373 | + // TODO this won't happen; remove from API | ||
374 | + log.debug("IOException broadcasting update", e); | ||
375 | + } | ||
376 | + } | ||
377 | + | ||
378 | + private void notifyPeers(InternalRemoveEvent event) { | ||
379 | + try { | ||
380 | + broadcastMessage(removeMessageSubject, event); | ||
381 | + } catch (IOException e) { | ||
382 | + // TODO this won't happen; remove from API | ||
383 | + log.debug("IOException broadcasting update", e); | ||
384 | + } | ||
385 | + } | ||
386 | + | ||
387 | + private void broadcastMessage(MessageSubject subject, Object event) throws | ||
388 | + IOException { | ||
389 | + ClusterMessage message = new ClusterMessage( | ||
390 | + clusterService.getLocalNode().id(), | ||
391 | + subject, | ||
392 | + serializer.encode(event)); | ||
393 | + clusterCommunicator.broadcast(message); | ||
394 | + } | ||
395 | + | ||
396 | + private void unicastMessage(NodeId peer, | ||
397 | + MessageSubject subject, | ||
398 | + Object event) throws IOException { | ||
399 | + ClusterMessage message = new ClusterMessage( | ||
400 | + clusterService.getLocalNode().id(), | ||
401 | + subject, | ||
402 | + serializer.encode(event)); | ||
403 | + clusterCommunicator.unicast(message, peer); | ||
404 | + } | ||
405 | + | ||
406 | + private final class Entry implements Map.Entry<K, V> { | ||
407 | + | ||
408 | + private final K key; | ||
409 | + private final V value; | ||
410 | + | ||
411 | + public Entry(K key, V value) { | ||
412 | + this.key = key; | ||
413 | + this.value = value; | ||
414 | + } | ||
415 | + | ||
416 | + @Override | ||
417 | + public K getKey() { | ||
418 | + return key; | ||
419 | + } | ||
420 | + | ||
421 | + @Override | ||
422 | + public V getValue() { | ||
423 | + return value; | ||
424 | + } | ||
425 | + | ||
426 | + @Override | ||
427 | + public V setValue(V value) { | ||
428 | + throw new UnsupportedOperationException(); | ||
429 | + } | ||
430 | + } | ||
431 | + | ||
432 | + private final class InternalPutEventListener implements | ||
433 | + ClusterMessageHandler { | ||
434 | + @Override | ||
435 | + public void handle(ClusterMessage message) { | ||
436 | + log.debug("Received put event from peer: {}", message.sender()); | ||
437 | + InternalPutEvent<K, V> event = serializer.decode(message.payload()); | ||
438 | + | ||
439 | + executor.submit(() -> { | ||
440 | + try { | ||
441 | + for (PutEntry<K, V> entry : event.entries()) { | ||
442 | + K key = entry.key(); | ||
443 | + V value = entry.value(); | ||
444 | + Timestamp timestamp = entry.timestamp(); | ||
445 | + | ||
446 | + if (putInternal(key, value, timestamp)) { | ||
447 | + EventuallyConsistentMapEvent externalEvent = | ||
448 | + new EventuallyConsistentMapEvent<>( | ||
449 | + EventuallyConsistentMapEvent.Type.PUT, key, | ||
450 | + value); | ||
451 | + notifyListeners(externalEvent); | ||
452 | + } | ||
453 | + } | ||
454 | + } catch (Exception e) { | ||
455 | + log.warn("Exception thrown handling put", e); | ||
456 | + } | ||
457 | + }); | ||
458 | + } | ||
459 | + } | ||
460 | + | ||
461 | + private final class InternalRemoveEventListener implements | ||
462 | + ClusterMessageHandler { | ||
463 | + @Override | ||
464 | + public void handle(ClusterMessage message) { | ||
465 | + log.debug("Received remove event from peer: {}", message.sender()); | ||
466 | + InternalRemoveEvent<K> event = serializer.decode(message.payload()); | ||
467 | + | ||
468 | + executor.submit(() -> { | ||
469 | + try { | ||
470 | + for (RemoveEntry<K> entry : event.entries()) { | ||
471 | + K key = entry.key(); | ||
472 | + Timestamp timestamp = entry.timestamp(); | ||
473 | + | ||
474 | + if (removeInternal(key, timestamp)) { | ||
475 | + EventuallyConsistentMapEvent externalEvent = new EventuallyConsistentMapEvent<K, V>( | ||
476 | + EventuallyConsistentMapEvent.Type.REMOVE, | ||
477 | + key, null); | ||
478 | + notifyListeners(externalEvent); | ||
479 | + } | ||
480 | + } | ||
481 | + } catch (Exception e) { | ||
482 | + log.warn("Exception thrown handling remove", e); | ||
483 | + } | ||
484 | + }); | ||
485 | + } | ||
486 | + } | ||
487 | + | ||
488 | + private static final class InternalPutEvent<K, V> { | ||
489 | + private final List<PutEntry<K, V>> entries; | ||
490 | + | ||
491 | + public InternalPutEvent(K key, V value, Timestamp timestamp) { | ||
492 | + entries = Collections | ||
493 | + .singletonList(new PutEntry<>(key, value, timestamp)); | ||
494 | + } | ||
495 | + | ||
496 | + public InternalPutEvent(List<PutEntry<K, V>> entries) { | ||
497 | + this.entries = checkNotNull(entries); | ||
498 | + } | ||
499 | + | ||
500 | + // Needed for serialization. | ||
501 | + @SuppressWarnings("unused") | ||
502 | + private InternalPutEvent() { | ||
503 | + entries = null; | ||
504 | + } | ||
505 | + | ||
506 | + public List<PutEntry<K, V>> entries() { | ||
507 | + return entries; | ||
508 | + } | ||
509 | + } | ||
510 | + | ||
511 | + private static final class PutEntry<K, V> { | ||
512 | + private final K key; | ||
513 | + private final V value; | ||
514 | + private final Timestamp timestamp; | ||
515 | + | ||
516 | + public PutEntry(K key, V value, Timestamp timestamp) { | ||
517 | + this.key = checkNotNull(key); | ||
518 | + this.value = checkNotNull(value); | ||
519 | + this.timestamp = checkNotNull(timestamp); | ||
520 | + } | ||
521 | + | ||
522 | + // Needed for serialization. | ||
523 | + @SuppressWarnings("unused") | ||
524 | + private PutEntry() { | ||
525 | + this.key = null; | ||
526 | + this.value = null; | ||
527 | + this.timestamp = null; | ||
528 | + } | ||
529 | + | ||
530 | + public K key() { | ||
531 | + return key; | ||
532 | + } | ||
533 | + | ||
534 | + public V value() { | ||
535 | + return value; | ||
536 | + } | ||
537 | + | ||
538 | + public Timestamp timestamp() { | ||
539 | + return timestamp; | ||
540 | + } | ||
541 | + | ||
542 | + public String toString() { | ||
543 | + return MoreObjects.toStringHelper(getClass()) | ||
544 | + .add("key", key) | ||
545 | + .add("value", value) | ||
546 | + .add("timestamp", timestamp) | ||
547 | + .toString(); | ||
548 | + } | ||
549 | + } | ||
550 | + | ||
551 | + private static final class InternalRemoveEvent<K> { | ||
552 | + private final List<RemoveEntry<K>> entries; | ||
553 | + | ||
554 | + public InternalRemoveEvent(K key, Timestamp timestamp) { | ||
555 | + entries = Collections.singletonList( | ||
556 | + new RemoveEntry<>(key, timestamp)); | ||
557 | + } | ||
558 | + | ||
559 | + public InternalRemoveEvent(List<RemoveEntry<K>> entries) { | ||
560 | + this.entries = checkNotNull(entries); | ||
561 | + } | ||
562 | + | ||
563 | + // Needed for serialization. | ||
564 | + @SuppressWarnings("unused") | ||
565 | + private InternalRemoveEvent() { | ||
566 | + entries = null; | ||
567 | + } | ||
568 | + | ||
569 | + public List<RemoveEntry<K>> entries() { | ||
570 | + return entries; | ||
571 | + } | ||
572 | + } | ||
573 | + | ||
574 | + private static final class RemoveEntry<K> { | ||
575 | + private final K key; | ||
576 | + private final Timestamp timestamp; | ||
577 | + | ||
578 | + public RemoveEntry(K key, Timestamp timestamp) { | ||
579 | + this.key = checkNotNull(key); | ||
580 | + this.timestamp = checkNotNull(timestamp); | ||
581 | + } | ||
582 | + | ||
583 | + // Needed for serialization. | ||
584 | + @SuppressWarnings("unused") | ||
585 | + private RemoveEntry() { | ||
586 | + this.key = null; | ||
587 | + this.timestamp = null; | ||
588 | + } | ||
589 | + | ||
590 | + public K key() { | ||
591 | + return key; | ||
592 | + } | ||
593 | + | ||
594 | + public Timestamp timestamp() { | ||
595 | + return timestamp; | ||
596 | + } | ||
597 | + } | ||
598 | +} |
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMapListener.java
0 → 100644
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 | +package org.onosproject.store.impl; | ||
17 | + | ||
18 | +/** | ||
19 | + * Listener interested in receiving modification events for an | ||
20 | + * EventuallyConsistentMap. | ||
21 | + */ | ||
22 | +public interface EventuallyConsistentMapListener { | ||
23 | + | ||
24 | + /** | ||
25 | + * Reacts to the specified event. | ||
26 | + * | ||
27 | + * @param event the event | ||
28 | + */ | ||
29 | + public void event(EventuallyConsistentMapEvent event); | ||
30 | +} |
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 | +package org.onosproject.store.impl; | ||
17 | + | ||
18 | +import org.onosproject.store.Timestamp; | ||
19 | + | ||
20 | +/** | ||
21 | + * A clock service which hands out wallclock-based timestamps. | ||
22 | + */ | ||
23 | +public class WallclockClockManager<T> implements ClockService<T> { | ||
24 | + @Override | ||
25 | + public Timestamp getTimestamp(T object) { | ||
26 | + return new WallClockTimestamp(); | ||
27 | + } | ||
28 | +} |
-
Please register or login to post a comment