mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-26 22:01:13 +01:00
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
This commit is contained in:
parent
adf8c48529
commit
db3af8974a
32
core/store/dist/src/main/java/org/onosproject/store/impl/ClockService.java
vendored
Normal file
32
core/store/dist/src/main/java/org/onosproject/store/impl/ClockService.java
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2015 Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.store.impl;
|
||||
|
||||
import org.onosproject.store.Timestamp;
|
||||
|
||||
/**
|
||||
* Clock service that can generate timestamps per object.
|
||||
*/
|
||||
public interface ClockService<T> {
|
||||
|
||||
/**
|
||||
* Gets a new timestamp for the given object.
|
||||
*
|
||||
* @param object Object to get a timestamp for
|
||||
* @return the new timestamp
|
||||
*/
|
||||
public Timestamp getTimestamp(T object);
|
||||
}
|
||||
171
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMap.java
vendored
Normal file
171
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMap.java
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2015 Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.store.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A distributed, eventually consistent map.
|
||||
*
|
||||
* This map does not offer read after writes consistency. Operations are
|
||||
* serialized via the timestamps issued by the clock service. If two updates
|
||||
* are in conflict, the update with the more recent timestamp will endure.
|
||||
*
|
||||
* The interface is mostly similar to {@link java.util.Map} with some minor
|
||||
* semantic changes and the addition of a listener framework (because the map
|
||||
* can be mutated by clients on other instances, not only through the local Java
|
||||
* API).
|
||||
*
|
||||
* Clients are expected to register an
|
||||
* {@link org.onosproject.store.impl.EventuallyConsistentMapListener} if they
|
||||
* are interested in receiving notifications of update to the map.
|
||||
*/
|
||||
public interface EventuallyConsistentMap<K, V> {
|
||||
|
||||
/**
|
||||
* Returns the number of key-value mappings in this map.
|
||||
*
|
||||
* @return number of key-value mappings
|
||||
*/
|
||||
public int size();
|
||||
|
||||
/**
|
||||
* Returns true if this map is empty.
|
||||
*
|
||||
* @return true if this map is empty, otherwise false
|
||||
*/
|
||||
public boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Returns true if the map contains a mapping for the specified key.
|
||||
*
|
||||
* @param key the key to check if this map contains
|
||||
* @return true if this map has a mapping for the key, otherwise false
|
||||
*/
|
||||
public boolean containsKey(K key);
|
||||
|
||||
/**
|
||||
* Returns true if the map contains a mapping from any key to the specified
|
||||
* value.
|
||||
*
|
||||
* @param value the value to check if this map has a mapping for
|
||||
* @return true if this map has a mapping to this value, otherwise false
|
||||
*/
|
||||
public boolean containsValue(V value);
|
||||
|
||||
/**
|
||||
* Returns the value mapped to the specified key.
|
||||
*
|
||||
* @param key the key to look up in this map
|
||||
* @return the value mapped to the key, or null if no mapping is found
|
||||
*/
|
||||
public V get(K key);
|
||||
|
||||
/**
|
||||
* Associates the specified value to the specified key in this map.
|
||||
* <p>
|
||||
* Note: this differs from the specification of {@link java.util.Map}
|
||||
* because it does not return the previous value associated with the key.
|
||||
* Clients are expected to register an
|
||||
* {@link org.onosproject.store.impl.EventuallyConsistentMapListener} if
|
||||
* they are interested in receiving notification of updates to the map.
|
||||
* </p>
|
||||
*
|
||||
* @param key the key to add a mapping for in this map
|
||||
* @param value the value to associate with the key in this map
|
||||
*/
|
||||
public void put(K key, V value);
|
||||
|
||||
/**
|
||||
* Removes the mapping associated with the specified key from the map.
|
||||
* <p>
|
||||
* Note: this differs from the specification of {@link java.util.Map}
|
||||
* because it does not return the previous value associated with the key.
|
||||
* Clients are expected to register an
|
||||
* {@link org.onosproject.store.impl.EventuallyConsistentMapListener} if
|
||||
* they are interested in receiving notification of updates to the map.
|
||||
* </p>
|
||||
*
|
||||
* @param key the key to remove the mapping for
|
||||
*/
|
||||
public void remove(K key);
|
||||
|
||||
/**
|
||||
* Adds mappings for all key-value pairs in the specified map to this map.
|
||||
* <p>
|
||||
* This will be more efficient in communication than calling individual put
|
||||
* operations.
|
||||
* </p>
|
||||
*
|
||||
* @param m a map of values to add to this map
|
||||
*/
|
||||
public void putAll(Map<? extends K, ? extends V> m);
|
||||
|
||||
/**
|
||||
* Removes all mappings from this map.
|
||||
*/
|
||||
public void clear();
|
||||
|
||||
/**
|
||||
* Returns a set of the keys in this map. Changes to the set are not
|
||||
* reflected back to the map.
|
||||
*
|
||||
* @return set of keys in the map
|
||||
*/
|
||||
public Set<K> keySet();
|
||||
|
||||
/**
|
||||
* Returns a collections of values in this map. Changes to the collection
|
||||
* are not reflected back to the map.
|
||||
*
|
||||
* @return collection of values in the map
|
||||
*/
|
||||
public Collection<V> values();
|
||||
|
||||
/**
|
||||
* Returns a set of mappings contained in this map. Changes to the set are
|
||||
* not reflected back to the map.
|
||||
*
|
||||
* @return set of key-value mappings in this map
|
||||
*/
|
||||
public Set<Map.Entry<K, V>> entrySet();
|
||||
|
||||
/**
|
||||
* Adds the specified listener to the map which will be notified whenever
|
||||
* the mappings in the map are changed.
|
||||
*
|
||||
* @param listener listener to register for events
|
||||
*/
|
||||
public void addListener(EventuallyConsistentMapListener listener);
|
||||
|
||||
/**
|
||||
* Removes the specified listener from the map such that it will no longer
|
||||
* receive change notifications.
|
||||
*
|
||||
* @param listener listener to deregister for events
|
||||
*/
|
||||
public void removeListener(EventuallyConsistentMapListener listener);
|
||||
|
||||
/**
|
||||
* Shuts down the map and breaks communication between different instances.
|
||||
* This allows the map objects to be cleaned up and garbage collected.
|
||||
* Calls to any methods on the map subsequent to calling destroy() will
|
||||
* throw a {@link java.lang.RuntimeException}.
|
||||
*/
|
||||
public void destroy();
|
||||
}
|
||||
71
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMapEvent.java
vendored
Normal file
71
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMapEvent.java
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2015 Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.store.impl;
|
||||
|
||||
/**
|
||||
* Event object signalling that the map was modified.
|
||||
*/
|
||||
public class EventuallyConsistentMapEvent<K, V> {
|
||||
|
||||
public enum Type {
|
||||
PUT,
|
||||
REMOVE
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final K key;
|
||||
private final V value;
|
||||
|
||||
/**
|
||||
* Creates a new event object.
|
||||
*
|
||||
* @param type the type of the event
|
||||
* @param key the key the event concerns
|
||||
* @param value the value related to the key, or null for remove events
|
||||
*/
|
||||
public EventuallyConsistentMapEvent(Type type, K key, V value) {
|
||||
this.type = type;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the event.
|
||||
*
|
||||
* @return the type of the event
|
||||
*/
|
||||
public Type type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key this event concerns.
|
||||
*
|
||||
* @return the key
|
||||
*/
|
||||
public K key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with this event.
|
||||
*
|
||||
* @return the value, or null if the event was REMOVE
|
||||
*/
|
||||
public V value() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
598
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMapImpl.java
vendored
Normal file
598
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMapImpl.java
vendored
Normal file
@ -0,0 +1,598 @@
|
||||
/*
|
||||
* Copyright 2015 Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.store.impl;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import org.onlab.util.KryoNamespace;
|
||||
import org.onosproject.cluster.ClusterService;
|
||||
import org.onosproject.cluster.NodeId;
|
||||
import org.onosproject.store.Timestamp;
|
||||
import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
|
||||
import org.onosproject.store.cluster.messaging.ClusterMessage;
|
||||
import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
|
||||
import org.onosproject.store.cluster.messaging.MessageSubject;
|
||||
import org.onosproject.store.serializers.KryoSerializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
|
||||
import static org.onlab.util.Tools.minPriority;
|
||||
import static org.onlab.util.Tools.namedThreads;
|
||||
|
||||
/**
|
||||
* Distributed Map implementation which uses optimistic replication and gossip
|
||||
* based techniques to provide an eventually consistent data store.
|
||||
*/
|
||||
public class EventuallyConsistentMapImpl<K, V>
|
||||
implements EventuallyConsistentMap<K, V> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(EventuallyConsistentMapImpl.class);
|
||||
|
||||
private final Map<K, Timestamped<V>> items;
|
||||
private final Map<K, Timestamp> removedItems;
|
||||
|
||||
private final String mapName;
|
||||
private final ClusterService clusterService;
|
||||
private final ClusterCommunicationService clusterCommunicator;
|
||||
private final KryoSerializer serializer;
|
||||
|
||||
private final ClockService<K> clockService;
|
||||
|
||||
private final MessageSubject updateMessageSubject;
|
||||
private final MessageSubject removeMessageSubject;
|
||||
|
||||
private final Set<EventuallyConsistentMapListener> listeners
|
||||
= new CopyOnWriteArraySet<>();
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
private final ScheduledExecutorService backgroundExecutor;
|
||||
|
||||
private volatile boolean destroyed = false;
|
||||
private static final String ERROR_DESTROYED = " is already destroyed";
|
||||
|
||||
// TODO: Make these anti-entropy params configurable
|
||||
private long initialDelaySec = 5;
|
||||
private long periodSec = 5;
|
||||
|
||||
/**
|
||||
* Creates a new eventually consistent map shared amongst multiple instances.
|
||||
*
|
||||
* Each map is identified by a string map name. EventuallyConsistentMapImpl
|
||||
* objects in different JVMs that use the same map name will form a
|
||||
* distributed map across JVMs (provided the cluster service is aware of
|
||||
* both nodes).
|
||||
*
|
||||
* The client is expected to provide an
|
||||
* {@link org.onlab.util.KryoNamespace.Builder} with which all classes that
|
||||
* will be stored in this map have been registered (including referenced
|
||||
* classes). This serializer will be used to serialize both K and V for
|
||||
* inter-node notifications.
|
||||
*
|
||||
* The client must provide an {@link org.onosproject.store.impl.ClockService}
|
||||
* which can generate timestamps for a given key. The clock service is free
|
||||
* to generate timestamps however it wishes, however these timestamps will
|
||||
* be used to serialize updates to the map so they must be strict enough
|
||||
* to ensure updates are properly ordered for the use case (i.e. in some
|
||||
* cases wallclock time will suffice, whereas in other cases logical time
|
||||
* will be necessary).
|
||||
*
|
||||
* @param mapName a String identifier for the map.
|
||||
* @param clusterService the cluster service
|
||||
* @param clusterCommunicator the cluster communications service
|
||||
* @param serializerBuilder a Kryo namespace builder that can serialize
|
||||
* both K and V
|
||||
* @param clockService a clock service able to generate timestamps
|
||||
* for K
|
||||
*/
|
||||
public EventuallyConsistentMapImpl(String mapName,
|
||||
ClusterService clusterService,
|
||||
ClusterCommunicationService clusterCommunicator,
|
||||
KryoNamespace.Builder serializerBuilder,
|
||||
ClockService<K> clockService) {
|
||||
|
||||
this.mapName = checkNotNull(mapName);
|
||||
this.clusterService = checkNotNull(clusterService);
|
||||
this.clusterCommunicator = checkNotNull(clusterCommunicator);
|
||||
|
||||
serializer = createSerializer(checkNotNull(serializerBuilder));
|
||||
|
||||
this.clockService = checkNotNull(clockService);
|
||||
|
||||
items = new ConcurrentHashMap<>();
|
||||
removedItems = new ConcurrentHashMap<>();
|
||||
|
||||
executor = Executors
|
||||
.newCachedThreadPool(namedThreads("onos-ecm-" + mapName + "-fg-%d"));
|
||||
|
||||
backgroundExecutor =
|
||||
newSingleThreadScheduledExecutor(minPriority(
|
||||
namedThreads("onos-ecm-" + mapName + "-bg-%d")));
|
||||
|
||||
updateMessageSubject = new MessageSubject("ecm-" + mapName + "-update");
|
||||
clusterCommunicator.addSubscriber(updateMessageSubject,
|
||||
new InternalPutEventListener());
|
||||
removeMessageSubject = new MessageSubject("ecm-" + mapName + "-remove");
|
||||
clusterCommunicator.addSubscriber(removeMessageSubject,
|
||||
new InternalRemoveEventListener());
|
||||
}
|
||||
|
||||
private KryoSerializer createSerializer(KryoNamespace.Builder builder) {
|
||||
return new KryoSerializer() {
|
||||
@Override
|
||||
protected void setupKryoPool() {
|
||||
// Add the map's internal helper classes to the user-supplied serializer
|
||||
serializerPool = builder
|
||||
.register(WallClockTimestamp.class)
|
||||
.register(PutEntry.class)
|
||||
.register(ArrayList.class)
|
||||
.register(InternalPutEvent.class)
|
||||
.register(InternalRemoveEvent.class)
|
||||
.build();
|
||||
|
||||
// TODO anti-entropy classes
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
return items.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
return items.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(K key) {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
return items.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(V value) {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
return items.values().stream()
|
||||
.anyMatch(timestamped -> timestamped.value().equals(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key) {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
Timestamped<V> value = items.get(key);
|
||||
if (value != null) {
|
||||
return value.value();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V value) {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
Timestamp timestamp = clockService.getTimestamp(key);
|
||||
if (putInternal(key, value, timestamp)) {
|
||||
notifyPeers(new InternalPutEvent<>(key, value, timestamp));
|
||||
EventuallyConsistentMapEvent<K, V> externalEvent
|
||||
= new EventuallyConsistentMapEvent<>(
|
||||
EventuallyConsistentMapEvent.Type.PUT, key, value);
|
||||
notifyListeners(externalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean putInternal(K key, V value, Timestamp timestamp) {
|
||||
synchronized (this) {
|
||||
Timestamp removed = removedItems.get(key);
|
||||
if (removed != null && removed.compareTo(timestamp) > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Timestamped<V> existing = items.get(key);
|
||||
if (existing != null && existing.isNewer(timestamp)) {
|
||||
return false;
|
||||
} else {
|
||||
items.put(key, new Timestamped<>(value, timestamp));
|
||||
removedItems.remove(key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(K key) {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
Timestamp timestamp = clockService.getTimestamp(key);
|
||||
if (removeInternal(key, timestamp)) {
|
||||
notifyPeers(new InternalRemoveEvent<>(key, timestamp));
|
||||
EventuallyConsistentMapEvent<K, V> externalEvent
|
||||
= new EventuallyConsistentMapEvent<>(
|
||||
EventuallyConsistentMapEvent.Type.REMOVE, key, null);
|
||||
notifyListeners(externalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean removeInternal(K key, Timestamp timestamp) {
|
||||
synchronized (this) {
|
||||
if (items.get(key) != null && items.get(key).isNewer(timestamp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
items.remove(key);
|
||||
removedItems.put(key, timestamp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
List<PutEntry<K, V>> updates = new ArrayList<>(m.size());
|
||||
|
||||
for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
|
||||
K key = entry.getKey();
|
||||
V value = entry.getValue();
|
||||
Timestamp timestamp = clockService.getTimestamp(entry.getKey());
|
||||
|
||||
if (putInternal(key, value, timestamp)) {
|
||||
updates.add(new PutEntry<>(key, value, timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
notifyPeers(new InternalPutEvent<>(updates));
|
||||
|
||||
for (PutEntry<K, V> entry : updates) {
|
||||
EventuallyConsistentMapEvent<K, V> externalEvent =
|
||||
new EventuallyConsistentMapEvent<>(
|
||||
EventuallyConsistentMapEvent.Type.PUT, entry.key(), entry.value());
|
||||
notifyListeners(externalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
List<RemoveEntry<K>> removed = new ArrayList<>(items.size());
|
||||
|
||||
for (K key : items.keySet()) {
|
||||
Timestamp timestamp = clockService.getTimestamp(key);
|
||||
|
||||
if (removeInternal(key, timestamp)) {
|
||||
removed.add(new RemoveEntry<>(key, timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
notifyPeers(new InternalRemoveEvent<>(removed));
|
||||
|
||||
for (RemoveEntry<K> entry : removed) {
|
||||
EventuallyConsistentMapEvent<K, V> externalEvent =
|
||||
new EventuallyConsistentMapEvent<>(
|
||||
EventuallyConsistentMapEvent.Type.REMOVE, entry.key(), null);
|
||||
notifyListeners(externalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
return items.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
return items.values().stream()
|
||||
.map(Timestamped::value)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<K, V>> entrySet() {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
return items.entrySet().stream()
|
||||
.map(e -> new Entry(e.getKey(), e.getValue().value()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(EventuallyConsistentMapListener listener) {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
listeners.add(checkNotNull(listener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(EventuallyConsistentMapListener listener) {
|
||||
checkState(destroyed, mapName + ERROR_DESTROYED);
|
||||
|
||||
listeners.remove(checkNotNull(listener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
destroyed = true;
|
||||
|
||||
executor.shutdown();
|
||||
backgroundExecutor.shutdown();
|
||||
|
||||
clusterCommunicator.removeSubscriber(updateMessageSubject);
|
||||
clusterCommunicator.removeSubscriber(removeMessageSubject);
|
||||
}
|
||||
|
||||
private void notifyListeners(EventuallyConsistentMapEvent event) {
|
||||
for (EventuallyConsistentMapListener listener : listeners) {
|
||||
listener.event(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyPeers(InternalPutEvent event) {
|
||||
try {
|
||||
log.debug("sending put {}", event);
|
||||
broadcastMessage(updateMessageSubject, event);
|
||||
} catch (IOException e) {
|
||||
// TODO this won't happen; remove from API
|
||||
log.debug("IOException broadcasting update", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyPeers(InternalRemoveEvent event) {
|
||||
try {
|
||||
broadcastMessage(removeMessageSubject, event);
|
||||
} catch (IOException e) {
|
||||
// TODO this won't happen; remove from API
|
||||
log.debug("IOException broadcasting update", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastMessage(MessageSubject subject, Object event) throws
|
||||
IOException {
|
||||
ClusterMessage message = new ClusterMessage(
|
||||
clusterService.getLocalNode().id(),
|
||||
subject,
|
||||
serializer.encode(event));
|
||||
clusterCommunicator.broadcast(message);
|
||||
}
|
||||
|
||||
private void unicastMessage(NodeId peer,
|
||||
MessageSubject subject,
|
||||
Object event) throws IOException {
|
||||
ClusterMessage message = new ClusterMessage(
|
||||
clusterService.getLocalNode().id(),
|
||||
subject,
|
||||
serializer.encode(event));
|
||||
clusterCommunicator.unicast(message, peer);
|
||||
}
|
||||
|
||||
private final class Entry implements Map.Entry<K, V> {
|
||||
|
||||
private final K key;
|
||||
private final V value;
|
||||
|
||||
public Entry(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private final class InternalPutEventListener implements
|
||||
ClusterMessageHandler {
|
||||
@Override
|
||||
public void handle(ClusterMessage message) {
|
||||
log.debug("Received put event from peer: {}", message.sender());
|
||||
InternalPutEvent<K, V> event = serializer.decode(message.payload());
|
||||
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
for (PutEntry<K, V> entry : event.entries()) {
|
||||
K key = entry.key();
|
||||
V value = entry.value();
|
||||
Timestamp timestamp = entry.timestamp();
|
||||
|
||||
if (putInternal(key, value, timestamp)) {
|
||||
EventuallyConsistentMapEvent externalEvent =
|
||||
new EventuallyConsistentMapEvent<>(
|
||||
EventuallyConsistentMapEvent.Type.PUT, key,
|
||||
value);
|
||||
notifyListeners(externalEvent);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Exception thrown handling put", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private final class InternalRemoveEventListener implements
|
||||
ClusterMessageHandler {
|
||||
@Override
|
||||
public void handle(ClusterMessage message) {
|
||||
log.debug("Received remove event from peer: {}", message.sender());
|
||||
InternalRemoveEvent<K> event = serializer.decode(message.payload());
|
||||
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
for (RemoveEntry<K> entry : event.entries()) {
|
||||
K key = entry.key();
|
||||
Timestamp timestamp = entry.timestamp();
|
||||
|
||||
if (removeInternal(key, timestamp)) {
|
||||
EventuallyConsistentMapEvent externalEvent = new EventuallyConsistentMapEvent<K, V>(
|
||||
EventuallyConsistentMapEvent.Type.REMOVE,
|
||||
key, null);
|
||||
notifyListeners(externalEvent);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Exception thrown handling remove", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final class InternalPutEvent<K, V> {
|
||||
private final List<PutEntry<K, V>> entries;
|
||||
|
||||
public InternalPutEvent(K key, V value, Timestamp timestamp) {
|
||||
entries = Collections
|
||||
.singletonList(new PutEntry<>(key, value, timestamp));
|
||||
}
|
||||
|
||||
public InternalPutEvent(List<PutEntry<K, V>> entries) {
|
||||
this.entries = checkNotNull(entries);
|
||||
}
|
||||
|
||||
// Needed for serialization.
|
||||
@SuppressWarnings("unused")
|
||||
private InternalPutEvent() {
|
||||
entries = null;
|
||||
}
|
||||
|
||||
public List<PutEntry<K, V>> entries() {
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PutEntry<K, V> {
|
||||
private final K key;
|
||||
private final V value;
|
||||
private final Timestamp timestamp;
|
||||
|
||||
public PutEntry(K key, V value, Timestamp timestamp) {
|
||||
this.key = checkNotNull(key);
|
||||
this.value = checkNotNull(value);
|
||||
this.timestamp = checkNotNull(timestamp);
|
||||
}
|
||||
|
||||
// Needed for serialization.
|
||||
@SuppressWarnings("unused")
|
||||
private PutEntry() {
|
||||
this.key = null;
|
||||
this.value = null;
|
||||
this.timestamp = null;
|
||||
}
|
||||
|
||||
public K key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public V value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Timestamp timestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(getClass())
|
||||
.add("key", key)
|
||||
.add("value", value)
|
||||
.add("timestamp", timestamp)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class InternalRemoveEvent<K> {
|
||||
private final List<RemoveEntry<K>> entries;
|
||||
|
||||
public InternalRemoveEvent(K key, Timestamp timestamp) {
|
||||
entries = Collections.singletonList(
|
||||
new RemoveEntry<>(key, timestamp));
|
||||
}
|
||||
|
||||
public InternalRemoveEvent(List<RemoveEntry<K>> entries) {
|
||||
this.entries = checkNotNull(entries);
|
||||
}
|
||||
|
||||
// Needed for serialization.
|
||||
@SuppressWarnings("unused")
|
||||
private InternalRemoveEvent() {
|
||||
entries = null;
|
||||
}
|
||||
|
||||
public List<RemoveEntry<K>> entries() {
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RemoveEntry<K> {
|
||||
private final K key;
|
||||
private final Timestamp timestamp;
|
||||
|
||||
public RemoveEntry(K key, Timestamp timestamp) {
|
||||
this.key = checkNotNull(key);
|
||||
this.timestamp = checkNotNull(timestamp);
|
||||
}
|
||||
|
||||
// Needed for serialization.
|
||||
@SuppressWarnings("unused")
|
||||
private RemoveEntry() {
|
||||
this.key = null;
|
||||
this.timestamp = null;
|
||||
}
|
||||
|
||||
public K key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public Timestamp timestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMapListener.java
vendored
Normal file
30
core/store/dist/src/main/java/org/onosproject/store/impl/EventuallyConsistentMapListener.java
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2015 Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.store.impl;
|
||||
|
||||
/**
|
||||
* Listener interested in receiving modification events for an
|
||||
* EventuallyConsistentMap.
|
||||
*/
|
||||
public interface EventuallyConsistentMapListener {
|
||||
|
||||
/**
|
||||
* Reacts to the specified event.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
public void event(EventuallyConsistentMapEvent event);
|
||||
}
|
||||
28
core/store/dist/src/main/java/org/onosproject/store/impl/WallclockClockManager.java
vendored
Normal file
28
core/store/dist/src/main/java/org/onosproject/store/impl/WallclockClockManager.java
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2015 Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.store.impl;
|
||||
|
||||
import org.onosproject.store.Timestamp;
|
||||
|
||||
/**
|
||||
* A clock service which hands out wallclock-based timestamps.
|
||||
*/
|
||||
public class WallclockClockManager<T> implements ClockService<T> {
|
||||
@Override
|
||||
public Timestamp getTimestamp(T object) {
|
||||
return new WallClockTimestamp();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user