Thomas Vachuska
Committed by Gerrit Code Review

Sketching out what link-state addition would look like; quite easy until we get …

…to the distributed store.
Added unit tests to provide durable-nondurable transitions.
FIxed issue where link could be accidentally activated.
Renamed parameter.

Change-Id: I8aa19a6583ec50dbf28769995f0a8ea9be9a4daa
......@@ -25,6 +25,17 @@ public final class AnnotationKeys {
private AnnotationKeys() {}
/**
* Annotation key for durable links.
*/
public static final String DURABLE = "durable";
/**
* Annotation key for active/inactive links. Links are implicitly
* considered active unless explicitly marked otherwise.
*/
public static final String INACTIVE = "inactive";
/**
* Annotation key for latency.
*/
public static final String LATENCY = "latency";
......
......@@ -20,6 +20,7 @@ import org.onlab.onos.net.provider.ProviderId;
import java.util.Objects;
import static com.google.common.base.MoreObjects.toStringHelper;
import static org.onlab.onos.net.Link.State.ACTIVE;
/**
* Default infrastructure link model implementation.
......@@ -29,22 +30,45 @@ public class DefaultLink extends AbstractModel implements Link {
private final ConnectPoint src;
private final ConnectPoint dst;
private final Type type;
private final State state;
private final boolean isDurable;
/**
* Creates an infrastructure link using the supplied information.
* Creates an active infrastructure link using the supplied information.
*
* @param providerId provider identity
* @param src link source
* @param dst link destination
* @param type link type
* @param providerId provider identity
* @param src link source
* @param dst link destination
* @param type link type
* @param annotations optional key/value annotations
*/
public DefaultLink(ProviderId providerId, ConnectPoint src, ConnectPoint dst,
Type type, Annotations... annotations) {
this(providerId, src, dst, type, ACTIVE, false, annotations);
}
/**
* Creates an infrastructure link using the supplied information.
* Links marked as durable will remain in the inventory when a vanish
* message is received and instead will be marked as inactive.
*
* @param providerId provider identity
* @param src link source
* @param dst link destination
* @param type link type
* @param state link state
* @param isDurable indicates if the link is to be considered durable
* @param annotations optional key/value annotations
*/
public DefaultLink(ProviderId providerId, ConnectPoint src, ConnectPoint dst,
Type type, State state,
boolean isDurable, Annotations... annotations) {
super(providerId, annotations);
this.src = src;
this.dst = dst;
this.type = type;
this.state = state;
this.isDurable = isDurable;
}
@Override
......@@ -63,6 +87,18 @@ public class DefaultLink extends AbstractModel implements Link {
}
@Override
public State state() {
return state;
}
@Override
public boolean isDurable() {
return isDurable;
}
// Note: Durability & state are purposefully omitted form equality & hashCode.
@Override
public int hashCode() {
return Objects.hash(src, dst, type);
}
......@@ -87,6 +123,8 @@ public class DefaultLink extends AbstractModel implements Link {
.add("src", src)
.add("dst", dst)
.add("type", type)
.add("state", state)
.add("durable", isDurable)
.toString();
}
......
......@@ -55,6 +55,23 @@ public interface Link extends Annotated, Provided, NetworkResource {
}
/**
* Representation of the link state, which applies primarily only to
* configured durable links, i.e. those that need to remain present,
* but instead be marked as inactive.
*/
public enum State {
/**
* Signifies that a link is currently active.
*/
ACTIVE,
/**
* Signifies that a link is currently active.
*/
INACTIVE
}
/**
* Returns the link source connection point.
*
* @return link source connection point
......@@ -75,4 +92,16 @@ public interface Link extends Annotated, Provided, NetworkResource {
*/
Type type();
/**
* Returns the link state.
*/
State state();
/**
* Indicates if the link is to be considered durable.
*
* @return link state
*/
boolean isDurable();
}
......
......@@ -34,9 +34,9 @@ public class DefaultLinkDescription extends AbstractDescription
/**
* Creates a link description using the supplied information.
*
* @param src link source
* @param dst link destination
* @param type link type
* @param src link source
* @param dst link destination
* @param type link type
* @param annotations optional key/value annotations
*/
public DefaultLinkDescription(ConnectPoint src, ConnectPoint dst,
......@@ -64,9 +64,10 @@ public class DefaultLinkDescription extends AbstractDescription
@Override
public String toString() {
return MoreObjects.toStringHelper("Link").add("src", src())
.add("dst", dst())
.add("type", type()).toString();
return MoreObjects.toStringHelper(this)
.add("src", src())
.add("dst", dst())
.add("type", type()).toString();
}
}
......
......@@ -45,6 +45,5 @@ public interface LinkDescription extends Description {
*/
Link.Type type();
// Add further link attributes
}
......
......@@ -33,7 +33,7 @@ public class LinkEvent extends AbstractEvent<LinkEvent.Type, Link> {
LINK_ADDED,
/**
* Signifies that a link has been updated.
* Signifies that a link has been updated or changed state.
*/
LINK_UPDATED,
......
......@@ -95,6 +95,16 @@ public interface LinkStore extends Store<LinkEvent, LinkStoreDelegate> {
LinkDescription linkDescription);
/**
* Removes the link, or marks it as inactive if the link is durable,
* based on the specified information.
*
* @param src link source
* @param dst link destination
* @return remove or update link event, or null if no change resulted
*/
LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst);
/**
* Removes the link based on the specified information.
*
* @param src link source
......@@ -103,4 +113,5 @@ public interface LinkStore extends Store<LinkEvent, LinkStoreDelegate> {
*/
LinkEvent removeLink(ConnectPoint src, ConnectPoint dst);
}
......
......@@ -78,5 +78,4 @@ public class LinkServiceAdapter implements LinkService {
public void removeListener(LinkListener listener) {
}
}
......
......@@ -158,7 +158,7 @@ public class LinkManager
if (deviceService.getRole(connectPoint.deviceId()) != MastershipRole.MASTER) {
return;
}
removeLinks(getLinks(connectPoint));
removeLinks(getLinks(connectPoint), false);
}
@Override
......@@ -166,7 +166,7 @@ public class LinkManager
if (deviceService.getRole(deviceId) != MastershipRole.MASTER) {
return;
}
removeLinks(getDeviceLinks(deviceId));
removeLinks(getDeviceLinks(deviceId), false);
}
@Override
......@@ -228,7 +228,7 @@ public class LinkManager
ConnectPoint src = linkDescription.src();
ConnectPoint dst = linkDescription.dst();
LinkEvent event = store.removeLink(src, dst);
LinkEvent event = store.removeOrDownLink(src, dst);
if (event != null) {
log.info("Link {} vanished", linkDescription);
post(event);
......@@ -242,7 +242,7 @@ public class LinkManager
log.info("Links for connection point {} vanished", connectPoint);
// FIXME: This will remove links registered by other providers
removeLinks(getLinks(connectPoint));
removeLinks(getLinks(connectPoint), true);
}
@Override
......@@ -251,14 +251,16 @@ public class LinkManager
checkValidity();
log.info("Links for device {} vanished", deviceId);
removeLinks(getDeviceLinks(deviceId));
removeLinks(getDeviceLinks(deviceId), true);
}
}
// Removes all links in the specified set and emits appropriate events.
private void removeLinks(Set<Link> links) {
private void removeLinks(Set<Link> links, boolean isSoftRemove) {
for (Link link : links) {
LinkEvent event = store.removeLink(link.src(), link.dst());
LinkEvent event = isSoftRemove ?
store.removeOrDownLink(link.src(), link.dst()) :
store.removeLink(link.src(), link.dst());
post(event);
}
}
......
......@@ -21,7 +21,6 @@ import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import org.apache.commons.lang3.RandomUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
......@@ -32,6 +31,7 @@ import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.cluster.ClusterService;
import org.onlab.onos.cluster.ControllerNode;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.net.AnnotationKeys;
import org.onlab.onos.net.AnnotationsUtil;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultAnnotations;
......@@ -65,28 +65,30 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
import static org.onlab.onos.net.DefaultAnnotations.union;
import static org.onlab.onos.net.DefaultAnnotations.merge;
import static org.onlab.onos.net.DefaultAnnotations.union;
import static org.onlab.onos.net.Link.State.ACTIVE;
import static org.onlab.onos.net.Link.State.INACTIVE;
import static org.onlab.onos.net.Link.Type.DIRECT;
import static org.onlab.onos.net.Link.Type.INDIRECT;
import static org.onlab.onos.net.LinkKey.linkKey;
import static org.onlab.onos.net.link.LinkEvent.Type.*;
import static org.onlab.onos.store.link.impl.GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT;
import static org.onlab.util.Tools.namedThreads;
import static org.slf4j.LoggerFactory.getLogger;
import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.notNull;
import static org.onlab.onos.store.link.impl.GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT;
/**
* Manages inventory of infrastructure links in distributed data store
......@@ -261,8 +263,6 @@ public class GossipLinkStore
mergedDesc = map.get(providerId);
}
if (event != null) {
log.info("Notifying peers of a link update topology event from providerId: "
+ "{} between src: {} and dst: {}",
......@@ -278,6 +278,26 @@ public class GossipLinkStore
return event;
}
@Override
public LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst) {
Link link = getLink(src, dst);
if (link == null) {
return null;
}
if (link.isDurable()) {
// FIXME: this is not the right thing to call for the gossip store; will not sync link state!!!
return link.state() == INACTIVE ? null :
updateLink(linkKey(link.src(), link.dst()), link,
new DefaultLink(link.providerId(),
link.src(), link.dst(),
link.type(), INACTIVE,
link.isDurable(),
link.annotations()));
}
return removeLink(src, dst);
}
private LinkEvent createOrUpdateLinkInternal(
ProviderId providerId,
Timestamped<LinkDescription> linkDescription) {
......@@ -333,7 +353,7 @@ public class GossipLinkStore
}
SparseAnnotations merged = union(existingLinkDescription.value().annotations(),
linkDescription.value().annotations());
newLinkDescription = new Timestamped<LinkDescription>(
newLinkDescription = new Timestamped<>(
new DefaultLinkDescription(
linkDescription.value().src(),
linkDescription.value().dst(),
......@@ -357,7 +377,8 @@ public class GossipLinkStore
private LinkEvent updateLink(LinkKey key, Link oldLink, Link newLink) {
// Note: INDIRECT -> DIRECT transition only
// so that BDDP discovered Link will not overwrite LDDP Link
if ((oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
if (oldLink.state() != newLink.state() ||
(oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
!AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) {
links.put(key, newLink);
......@@ -449,12 +470,8 @@ public class GossipLinkStore
// outdated remove request, ignore
return null;
}
Link link = links.get(key);
if (isDurable(link)) {
return null;
}
removedLinks.put(key, timestamp);
links.remove(key);
Link link = links.remove(key);
linkDescriptions.clear();
if (link != null) {
srcLinks.remove(link.src().deviceId(), key);
......@@ -465,11 +482,6 @@ public class GossipLinkStore
}
}
// Indicates if the link has been marked as durable via annotations.
private boolean isDurable(Link link) {
return link != null && Objects.equals(link.annotations().value("durable"), "true");
}
private static <K, V> SetMultimap<K, V> createSynchronizedHashMultiMap() {
return synchronizedSetMultimap(HashMultimap.<K, V>create());
}
......@@ -518,7 +530,10 @@ public class GossipLinkStore
annotations = merge(annotations, e.getValue().value().annotations());
}
return new DefaultLink(baseProviderId, src, dst, type, annotations);
boolean isDurable = Objects.equals(annotations.value(AnnotationKeys.DURABLE), "true");
boolean isActive = !Objects.equals(annotations.value(AnnotationKeys.INACTIVE), "true");
return new DefaultLink(baseProviderId, src, dst, type,
isActive ? ACTIVE : INACTIVE, isDurable, annotations);
}
private Map<ProviderId, Timestamped<LinkDescription>> getOrCreateLinkDescriptions(LinkKey key) {
......@@ -538,9 +553,11 @@ public class GossipLinkStore
}
private final Function<LinkKey, Link> lookupLink = new LookupLink();
/**
* Returns a Function to lookup Link instance using LinkKey from cache.
* @return
*
* @return lookup link function
*/
private Function<LinkKey, Link> lookupLink() {
return lookupLink;
......
......@@ -49,6 +49,8 @@ import static com.google.common.collect.ImmutableSetMultimap.Builder;
import static org.onlab.graph.GraphPathSearch.Result;
import static org.onlab.graph.TarjanGraphSearch.SCCResult;
import static org.onlab.onos.core.CoreService.CORE_PROVIDER_ID;
import static org.onlab.onos.net.Link.State.ACTIVE;
import static org.onlab.onos.net.Link.State.INACTIVE;
import static org.onlab.onos.net.Link.Type.INDIRECT;
/**
......@@ -434,7 +436,8 @@ public class DefaultTopology extends AbstractModel implements Topology {
public double weight(TopologyEdge edge) {
// To force preference to use direct paths first, make indirect
// links as expensive as the linear vertex traversal.
return edge.link().type() == INDIRECT ? indirectLinkCost : 1;
return edge.link().state() == ACTIVE ?
(edge.link().type() == INDIRECT ? indirectLinkCost : 1) : -1;
}
}
......@@ -442,7 +445,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
private static class NoIndirectLinksWeight implements LinkWeight {
@Override
public double weight(TopologyEdge edge) {
return edge.link().type() == INDIRECT ? -1 : 1;
return edge.link().state() == INACTIVE || edge.link().type() == INDIRECT ? -1 : 1;
}
}
......
......@@ -15,15 +15,15 @@
*/
package org.onlab.onos.store.serializers;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultLink;
import org.onlab.onos.net.Link.Type;
import org.onlab.onos.net.provider.ProviderId;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultLink;
import org.onlab.onos.net.Link.State;
import org.onlab.onos.net.Link.Type;
import org.onlab.onos.net.provider.ProviderId;
/**
* Kryo Serializer for {@link DefaultLink}.
......@@ -44,6 +44,8 @@ public class DefaultLinkSerializer extends Serializer<DefaultLink> {
kryo.writeClassAndObject(output, object.src());
kryo.writeClassAndObject(output, object.dst());
kryo.writeClassAndObject(output, object.type());
kryo.writeClassAndObject(output, object.state());
kryo.writeClassAndObject(output, object.isDurable());
}
@Override
......@@ -52,6 +54,8 @@ public class DefaultLinkSerializer extends Serializer<DefaultLink> {
ConnectPoint src = (ConnectPoint) kryo.readClassAndObject(input);
ConnectPoint dst = (ConnectPoint) kryo.readClassAndObject(input);
Type linkType = (Type) kryo.readClassAndObject(input);
return new DefaultLink(providerId, src, dst, linkType);
State state = (State) kryo.readClassAndObject(input);
boolean isDurable = (boolean) kryo.readClassAndObject(input);
return new DefaultLink(providerId, src, dst, linkType, state, isDurable);
}
}
......
......@@ -164,6 +164,7 @@ public final class KryoNamespaces {
DefaultPortDescription.class,
Element.class,
Link.Type.class,
Link.State.class,
Timestamp.class,
HostId.class,
HostDescription.class,
......
......@@ -49,6 +49,8 @@ import static com.google.common.collect.ImmutableSetMultimap.Builder;
import static org.onlab.graph.GraphPathSearch.Result;
import static org.onlab.graph.TarjanGraphSearch.SCCResult;
import static org.onlab.onos.core.CoreService.CORE_PROVIDER_ID;
import static org.onlab.onos.net.Link.State.ACTIVE;
import static org.onlab.onos.net.Link.State.INACTIVE;
import static org.onlab.onos.net.Link.Type.INDIRECT;
/**
......@@ -434,7 +436,8 @@ public class DefaultTopology extends AbstractModel implements Topology {
public double weight(TopologyEdge edge) {
// To force preference to use direct paths first, make indirect
// links as expensive as the linear vertex traversal.
return edge.link().type() == INDIRECT ? indirectLinkCost : 1;
return edge.link().state() == ACTIVE ?
(edge.link().type() == INDIRECT ? indirectLinkCost : 1) : -1;
}
}
......@@ -442,7 +445,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
private static class NoIndirectLinksWeight implements LinkWeight {
@Override
public double weight(TopologyEdge edge) {
return edge.link().type() == INDIRECT ? -1 : 1;
return edge.link().state() == INACTIVE || edge.link().type() == INDIRECT ? -1 : 1;
}
}
......
......@@ -19,20 +19,20 @@ import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.net.AnnotationKeys;
import org.onlab.onos.net.AnnotationsUtil;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultAnnotations;
import org.onlab.onos.net.DefaultLink;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.SparseAnnotations;
import org.onlab.onos.net.Link.Type;
import org.onlab.onos.net.LinkKey;
import org.onlab.onos.net.SparseAnnotations;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.link.LinkDescription;
import org.onlab.onos.net.link.LinkEvent;
......@@ -46,22 +46,24 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static org.onlab.onos.net.DefaultAnnotations.union;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.base.Verify.verifyNotNull;
import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
import static org.onlab.onos.net.DefaultAnnotations.merge;
import static org.onlab.onos.net.DefaultAnnotations.union;
import static org.onlab.onos.net.Link.State.ACTIVE;
import static org.onlab.onos.net.Link.State.INACTIVE;
import static org.onlab.onos.net.Link.Type.DIRECT;
import static org.onlab.onos.net.Link.Type.INDIRECT;
import static org.onlab.onos.net.LinkKey.linkKey;
import static org.onlab.onos.net.link.LinkEvent.Type.*;
import static org.slf4j.LoggerFactory.getLogger;
import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.base.Verify.verifyNotNull;
/**
* Manages inventory of infrastructure links using trivial in-memory structures
......@@ -77,7 +79,7 @@ public class SimpleLinkStore
// Link inventory
private final ConcurrentMap<LinkKey, Map<ProviderId, LinkDescription>>
linkDescs = new ConcurrentHashMap<>();
linkDescs = new ConcurrentHashMap<>();
// Link instance cache
private final ConcurrentMap<LinkKey, Link> links = new ConcurrentHashMap<>();
......@@ -116,9 +118,9 @@ public class SimpleLinkStore
// lock for iteration
synchronized (srcLinks) {
return FluentIterable.from(srcLinks.get(deviceId))
.transform(lookupLink())
.filter(notNull())
.toSet();
.transform(lookupLink())
.filter(notNull())
.toSet();
}
}
......@@ -127,9 +129,9 @@ public class SimpleLinkStore
// lock for iteration
synchronized (dstLinks) {
return FluentIterable.from(dstLinks.get(deviceId))
.transform(lookupLink())
.filter(notNull())
.toSet();
.transform(lookupLink())
.filter(notNull())
.toSet();
}
}
......@@ -178,11 +180,30 @@ public class SimpleLinkStore
}
}
@Override
public LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst) {
Link link = getLink(src, dst);
if (link == null) {
return null;
}
if (link.isDurable()) {
return link.state() == INACTIVE ? null :
updateLink(linkKey(link.src(), link.dst()), link,
new DefaultLink(link.providerId(),
link.src(), link.dst(),
link.type(), INACTIVE,
link.isDurable(),
link.annotations()));
}
return removeLink(src, dst);
}
// Guarded by linkDescs value (=locking each Link)
private LinkDescription createOrUpdateLinkDescription(
Map<ProviderId, LinkDescription> descs,
ProviderId providerId,
LinkDescription linkDescription) {
Map<ProviderId, LinkDescription> descs,
ProviderId providerId,
LinkDescription linkDescription) {
// merge existing attributes and merge
LinkDescription oldDesc = descs.get(providerId);
......@@ -196,11 +217,10 @@ public class SimpleLinkStore
newType = linkDescription.type();
}
SparseAnnotations merged = union(oldDesc.annotations(),
linkDescription.annotations());
newDesc = new DefaultLinkDescription(
linkDescription.src(),
linkDescription.dst(),
newType, merged);
linkDescription.annotations());
newDesc = new DefaultLinkDescription(linkDescription.src(),
linkDescription.dst(),
newType, merged);
}
return descs.put(providerId, newDesc);
}
......@@ -217,8 +237,9 @@ public class SimpleLinkStore
// Updates, if necessary the specified link and returns the appropriate event.
// Guarded by linkDescs value (=locking each Link)
private LinkEvent updateLink(LinkKey key, Link oldLink, Link newLink) {
if ((oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
!AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) {
if (oldLink.state() != newLink.state() ||
(oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
!AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) {
links.put(key, newLink);
// strictly speaking following can be ommitted
......@@ -234,11 +255,7 @@ public class SimpleLinkStore
final LinkKey key = linkKey(src, dst);
Map<ProviderId, LinkDescription> descs = getOrCreateLinkDescriptions(key);
synchronized (descs) {
Link link = links.get(key);
if (isDurable(link)) {
return null;
}
links.remove(key);
Link link = links.remove(key);
descs.clear();
if (link != null) {
srcLinks.remove(link.src().deviceId(), key);
......@@ -249,11 +266,6 @@ public class SimpleLinkStore
}
}
// Indicates if the link has been marked as durable via annotations.
private boolean isDurable(Link link) {
return link != null && Objects.equals(link.annotations().value("durable"), "true");
}
private static <K, V> SetMultimap<K, V> createSynchronizedHashMultiMap() {
return synchronizedSetMultimap(HashMultimap.<K, V>create());
}
......@@ -301,7 +313,10 @@ public class SimpleLinkStore
annotations = merge(annotations, e.getValue().annotations());
}
return new DefaultLink(primary , src, dst, type, annotations);
boolean isDurable = Objects.equals(annotations.value(AnnotationKeys.DURABLE), "true");
boolean isActive = !Objects.equals(annotations.value(AnnotationKeys.INACTIVE), "true");
return new DefaultLink(primary, src, dst, type,
isActive ? ACTIVE : INACTIVE, isDurable, annotations);
}
private Map<ProviderId, LinkDescription> getOrCreateLinkDescriptions(LinkKey key) {
......@@ -321,6 +336,7 @@ public class SimpleLinkStore
}
private final Function<LinkKey, Link> lookupLink = new LookupLink();
private Function<LinkKey, Link> lookupLink() {
return lookupLink;
}
......
......@@ -23,6 +23,7 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.onlab.onos.net.AnnotationKeys;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultAnnotations;
import org.onlab.onos.net.DeviceId;
......@@ -80,6 +81,23 @@ public class SimpleLinkStoreTest {
.set("B4", "b4")
.build();
private static final SparseAnnotations DA1 = DefaultAnnotations.builder()
.set("A1", "a1")
.set("B1", "b1")
.set(AnnotationKeys.DURABLE, "true")
.build();
private static final SparseAnnotations DA2 = DefaultAnnotations.builder()
.set("A2", "a2")
.set("B2", "b2")
.set(AnnotationKeys.DURABLE, "true")
.build();
private static final SparseAnnotations NDA1 = DefaultAnnotations.builder()
.set("A1", "a1")
.set("B1", "b1")
.remove(AnnotationKeys.DURABLE)
.build();
private SimpleLinkStore simpleLinkStore;
private LinkStore linkStore;
......@@ -105,17 +123,19 @@ public class SimpleLinkStoreTest {
}
private void putLink(DeviceId srcId, PortNumber srcNum,
DeviceId dstId, PortNumber dstNum, Type type,
DeviceId dstId, PortNumber dstNum,
Type type, boolean isDurable,
SparseAnnotations... annotations) {
ConnectPoint src = new ConnectPoint(srcId, srcNum);
ConnectPoint dst = new ConnectPoint(dstId, dstNum);
linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type, annotations));
linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type,
annotations));
}
private void putLink(LinkKey key, Type type, SparseAnnotations... annotations) {
putLink(key.src().deviceId(), key.src().port(),
key.dst().deviceId(), key.dst().port(),
type, annotations);
type, false, annotations);
}
private static void assertLink(DeviceId srcId, PortNumber srcNum,
......@@ -138,9 +158,9 @@ public class SimpleLinkStoreTest {
public final void testGetLinkCount() {
assertEquals("initialy empty", 0, linkStore.getLinkCount());
putLink(DID1, P1, DID2, P2, DIRECT);
putLink(DID2, P2, DID1, P1, DIRECT);
putLink(DID1, P1, DID2, P2, DIRECT);
putLink(DID1, P1, DID2, P2, DIRECT, false);
putLink(DID2, P2, DID1, P1, DIRECT, false);
putLink(DID1, P1, DID2, P2, DIRECT, false);
assertEquals("expecting 2 unique link", 2, linkStore.getLinkCount());
}
......@@ -360,6 +380,47 @@ public class SimpleLinkStoreTest {
@Test
public final void testRemoveOrDownLink() {
removeOrDownLink(false);
}
@Test
public final void testRemoveOrDownLinkDurable() {
removeOrDownLink(true);
}
private void removeOrDownLink(boolean isDurable) {
final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1);
putLink(linkId1, DIRECT, isDurable ? DA1 : A1);
putLink(linkId2, DIRECT, isDurable ? DA2 : A2);
// DID1,P1 => DID2,P2
// DID2,P2 => DID1,P1
// DID1,P2 => DID2,P3
LinkEvent event = linkStore.removeOrDownLink(d1P1, d2P2);
assertEquals(isDurable ? LINK_UPDATED : LINK_REMOVED, event.type());
assertAnnotationsEquals(event.subject().annotations(), isDurable ? DA1 : A1);
LinkEvent event2 = linkStore.removeOrDownLink(d1P1, d2P2);
assertNull(event2);
assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1));
assertAnnotationsEquals(linkStore.getLink(d2P2, d1P1).annotations(),
isDurable ? DA2 : A2);
// annotations, etc. should not survive remove
if (!isDurable) {
putLink(linkId1, DIRECT);
assertLink(linkId1, DIRECT, linkStore.getLink(d1P1, d2P2));
assertAnnotationsEquals(linkStore.getLink(d1P1, d2P2).annotations());
}
}
@Test
public final void testRemoveLink() {
final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
......@@ -402,6 +463,30 @@ public class SimpleLinkStoreTest {
assertNotNull(linkStore.getLink(src, dst));
}
@Test
public void testDurableToNonDurable() {
final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
putLink(linkId1, DIRECT, DA1);
assertTrue("should be be durable", linkStore.getLink(d1P1, d2P2).isDurable());
putLink(linkId1, DIRECT, NDA1);
assertFalse("should not be durable", linkStore.getLink(d1P1, d2P2).isDurable());
}
@Test
public void testNonDurableToDurable() {
final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
putLink(linkId1, DIRECT, A1);
assertFalse("should not be durable", linkStore.getLink(d1P1, d2P2).isDurable());
putLink(linkId1, DIRECT, DA1);
assertTrue("should be durable", linkStore.getLink(d1P1, d2P2).isDurable());
}
// If Delegates should be called only on remote events,
// then Simple* should never call them, thus not test required.
@Ignore("Ignore until Delegate spec. is clear.")
......@@ -451,7 +536,7 @@ public class SimpleLinkStoreTest {
linkStore.unsetDelegate(checkUpdate);
linkStore.setDelegate(checkRemove);
linkStore.removeLink(d1P1, d2P2);
linkStore.removeOrDownLink(d1P1, d2P2);
assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
}
}
......