Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
Conflicts:
core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java
Change-Id: I6a8fc884f0c0b6578762d7b439177378e838735f
diff --git a/apps/fwd/pom.xml b/apps/fwd/pom.xml
index 4ee2dc3..b203121 100644
--- a/apps/fwd/pom.xml
+++ b/apps/fwd/pom.xml
@@ -16,4 +16,11 @@
<description>ONOS simple reactive forwarding app</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ </dependencies>
+
</project>
diff --git a/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java b/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java
index 8ead67f..62b0b84 100644
--- a/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java
+++ b/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java
@@ -1,12 +1,10 @@
package org.onlab.onos.fwd;
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.Set;
-
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.Modified;
+import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.onos.ApplicationId;
@@ -29,8 +27,14 @@
import org.onlab.onos.net.packet.PacketService;
import org.onlab.onos.net.topology.TopologyService;
import org.onlab.packet.Ethernet;
+import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
+import java.util.Dictionary;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
/**
* Sample reactive forwarding application.
*/
@@ -61,6 +65,10 @@
private ApplicationId appId;
+ @Property(name = "enabled", boolValue = true,
+ label = "Enable forwarding; default is true")
+ private boolean isEnabled = true;
+
@Activate
public void activate() {
appId = coreService.registerApplication("org.onlab.onos.fwd");
@@ -76,6 +84,22 @@
log.info("Stopped");
}
+ @Modified
+ public void modified(ComponentContext context) {
+ Dictionary properties = context.getProperties();
+ String flag = (String) properties.get("enabled");
+ if (flag != null) {
+ boolean enabled = flag.equals("true");
+ if (isEnabled != enabled) {
+ isEnabled = enabled;
+ if (!isEnabled) {
+ flowRuleService.removeFlowRulesById(appId);
+ }
+ log.info("Reconfigured. Forwarding is {}",
+ isEnabled ? "enabled" : "disabled");
+ }
+ }
+ }
/**
* Packet processor responsible for forwarding packets along their paths.
@@ -86,7 +110,7 @@
public void process(PacketContext context) {
// Stop processing if the packet has been handled, since we
// can't do any more to it.
- if (context.isHandled()) {
+ if (!isEnabled || context.isHandled()) {
return;
}
@@ -114,8 +138,8 @@
// Otherwise, get a set of paths that lead from here to the
// destination edge switch.
Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(),
- pkt.receivedFrom().deviceId(),
- dst.location().deviceId());
+ pkt.receivedFrom().deviceId(),
+ dst.location().deviceId());
if (paths.isEmpty()) {
// If there are no paths, flood and bail.
flood(context);
@@ -127,8 +151,8 @@
Path path = pickForwardPath(paths, pkt.receivedFrom().port());
if (path == null) {
log.warn("Doh... don't know where to go... {} -> {} received on {}",
- ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
- pkt.receivedFrom());
+ ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
+ pkt.receivedFrom());
flood(context);
return;
}
@@ -152,7 +176,7 @@
// Floods the specified packet if permissible.
private void flood(PacketContext context) {
if (topologyService.isBroadcastPoint(topologyService.currentTopology(),
- context.inPacket().receivedFrom())) {
+ context.inPacket().receivedFrom())) {
packetOut(context, PortNumber.FLOOD);
} else {
context.block();
@@ -174,18 +198,17 @@
Ethernet inPkt = context.inPacket().parsed();
TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
builder.matchEthType(inPkt.getEtherType())
- .matchEthSrc(inPkt.getSourceMAC())
- .matchEthDst(inPkt.getDestinationMAC())
- .matchInport(context.inPacket().receivedFrom().port());
+ .matchEthSrc(inPkt.getSourceMAC())
+ .matchEthDst(inPkt.getDestinationMAC())
+ .matchInport(context.inPacket().receivedFrom().port());
TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder();
treat.setOutput(portNumber);
FlowRule f = new DefaultFlowRule(context.inPacket().receivedFrom().deviceId(),
- builder.build(), treat.build(), PRIORITY, appId, TIMEOUT);
+ builder.build(), treat.build(), PRIORITY, appId, TIMEOUT);
flowRuleService.applyFlowRules(f);
-
}
}
diff --git a/apps/sdnip/pom.xml b/apps/sdnip/pom.xml
index c8db20d..390b429 100644
--- a/apps/sdnip/pom.xml
+++ b/apps/sdnip/pom.xml
@@ -36,6 +36,12 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onlab-thirdparty</artifactId>
+ </dependency>
+
</dependencies>
</project>
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/PeerConnectivity.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/PeerConnectivity.java
index e17206d..31cd971 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/PeerConnectivity.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/PeerConnectivity.java
@@ -126,8 +126,8 @@
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_TCP)
- .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
- .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
+ .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
+ .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
.matchTcpDst(BGP_PORT)
.build();
@@ -147,8 +147,8 @@
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_TCP)
- .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
- .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
+ .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
+ .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
.matchTcpSrc(BGP_PORT)
.build();
@@ -165,8 +165,8 @@
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_TCP)
- .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
- .matchIPDst(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
+ .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
+ .matchIPDst(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
.matchTcpDst(BGP_PORT)
.build();
@@ -183,8 +183,8 @@
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_TCP)
- .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
- .matchIPDst(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
+ .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
+ .matchIPDst(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
.matchTcpSrc(BGP_PORT)
.build();
@@ -251,8 +251,8 @@
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_ICMP)
- .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
- .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
+ .matchIPSrc(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
+ .matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
@@ -269,8 +269,8 @@
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_ICMP)
- .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
- .matchIPDst(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
+ .matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
+ .matchIPDst(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
.build();
PointToPointIntent reversedIntent = new PointToPointIntent(
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteEntry.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteEntry.java
new file mode 100644
index 0000000..f8d659c
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteEntry.java
@@ -0,0 +1,100 @@
+package org.onlab.onos.sdnip;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Represents a route entry for an IP prefix.
+ */
+public class RouteEntry {
+ private final IpPrefix prefix; // The IP prefix
+ private final IpAddress nextHop; // Next-hop IP address
+
+ /**
+ * Class constructor.
+ *
+ * @param prefix the IP prefix of the route
+ * @param nextHop the next hop IP address for the route
+ */
+ public RouteEntry(IpPrefix prefix, IpAddress nextHop) {
+ this.prefix = checkNotNull(prefix);
+ this.nextHop = checkNotNull(nextHop);
+ }
+
+ /**
+ * Returns the IP prefix of the route.
+ *
+ * @return the IP prefix of the route
+ */
+ public IpPrefix prefix() {
+ return prefix;
+ }
+
+ /**
+ * Returns the next hop IP address for the route.
+ *
+ * @return the next hop IP address for the route
+ */
+ public IpAddress nextHop() {
+ return nextHop;
+ }
+
+ /**
+ * Creates the binary string representation of an IPv4 prefix.
+ * The string length is equal to the prefix length.
+ *
+ * @param ip4Prefix the IPv4 prefix to use
+ * @return the binary string representation
+ */
+ static String createBinaryString(IpPrefix ip4Prefix) {
+ if (ip4Prefix.prefixLength() == 0) {
+ return "";
+ }
+
+ StringBuilder result = new StringBuilder(ip4Prefix.prefixLength());
+ long value = ip4Prefix.toInt();
+ for (int i = 0; i < ip4Prefix.prefixLength(); i++) {
+ long mask = 1 << (IpAddress.MAX_INET_MASK - 1 - i);
+ result.append(((value & mask) == 0) ? "0" : "1");
+ }
+ return result.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ //
+ // NOTE: Subclasses are considered as change of identity, hence
+ // equals() will return false if the class type doesn't match.
+ //
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ RouteEntry otherRoute = (RouteEntry) other;
+ return Objects.equals(this.prefix, otherRoute.prefix) &&
+ Objects.equals(this.nextHop, otherRoute.nextHop);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(prefix, nextHop);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("prefix", prefix)
+ .add("nextHop", nextHop)
+ .toString();
+ }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteListener.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteListener.java
new file mode 100644
index 0000000..424e348
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteListener.java
@@ -0,0 +1,13 @@
+package org.onlab.onos.sdnip;
+
+/**
+ * An interface to receive route updates from route providers.
+ */
+public interface RouteListener {
+ /**
+ * Receives a route update from a route provider.
+ *
+ * @param routeUpdate the updated route information
+ */
+ public void update(RouteUpdate routeUpdate);
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteUpdate.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteUpdate.java
new file mode 100644
index 0000000..a134a7a
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/RouteUpdate.java
@@ -0,0 +1,91 @@
+package org.onlab.onos.sdnip;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Represents a change in routing information.
+ */
+public class RouteUpdate {
+ private final Type type; // The route update type
+ private final RouteEntry routeEntry; // The updated route entry
+
+ /**
+ * Specifies the type of a route update.
+ * <p/>
+ * Route updates can either provide updated information for a route, or
+ * withdraw a previously updated route.
+ */
+ public enum Type {
+ /**
+ * The update contains updated route information for a route.
+ */
+ UPDATE,
+ /**
+ * The update withdraws the route, meaning any previous information is
+ * no longer valid.
+ */
+ DELETE
+ }
+
+ /**
+ * Class constructor.
+ *
+ * @param type the type of the route update
+ * @param routeEntry the route entry with the update
+ */
+ public RouteUpdate(Type type, RouteEntry routeEntry) {
+ this.type = type;
+ this.routeEntry = checkNotNull(routeEntry);
+ }
+
+ /**
+ * Returns the type of the route update.
+ *
+ * @return the type of the update
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the route entry the route update is for.
+ *
+ * @return the route entry the route update is for
+ */
+ public RouteEntry routeEntry() {
+ return routeEntry;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof RouteUpdate)) {
+ return false;
+ }
+
+ RouteUpdate otherUpdate = (RouteUpdate) other;
+
+ return Objects.equals(this.type, otherUpdate.type) &&
+ Objects.equals(this.routeEntry, otherUpdate.routeEntry);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, routeEntry);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("type", type)
+ .add("routeEntry", routeEntry)
+ .toString();
+ }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
new file mode 100644
index 0000000..2a1a655
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
@@ -0,0 +1,769 @@
+package org.onlab.onos.sdnip;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Host;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
+import org.onlab.onos.net.flow.criteria.Criterion;
+import org.onlab.onos.net.flow.criteria.Criterion.Type;
+import org.onlab.onos.net.host.HostEvent;
+import org.onlab.onos.net.host.HostListener;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
+import org.onlab.onos.sdnip.config.BgpPeer;
+import org.onlab.onos.sdnip.config.Interface;
+import org.onlab.onos.sdnip.config.SdnIpConfigService;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.googlecode.concurrenttrees.common.KeyValuePair;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+
+/**
+ * This class processes BGP route update, translates each update into a intent
+ * and submits the intent.
+ *
+ * TODO: Make it thread-safe.
+ */
+public class Router implements RouteListener {
+
+ private static final Logger log = LoggerFactory.getLogger(Router.class);
+
+ // Store all route updates in a InvertedRadixTree.
+ // The key in this Tree is the binary sting of prefix of route.
+ // The Ip4Address is the next hop address of route, and is also the value
+ // of each entry.
+ private InvertedRadixTree<RouteEntry> bgpRoutes;
+
+ // Stores all incoming route updates in a queue.
+ private BlockingQueue<RouteUpdate> routeUpdates;
+
+ // The Ip4Address is the next hop address of each route update.
+ private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
+ private ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent> pushedRouteIntents;
+
+ private IntentService intentService;
+ //private IProxyArpService proxyArp;
+ private HostService hostService;
+ private SdnIpConfigService configInfoService;
+ private InterfaceService interfaceService;
+
+ private ExecutorService bgpUpdatesExecutor;
+ private ExecutorService bgpIntentsSynchronizerExecutor;
+
+ // TODO temporary
+ private int intentId = Integer.MAX_VALUE / 2;
+
+ //
+ // State to deal with SDN-IP Leader election and pushing Intents
+ //
+ private Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
+ private volatile boolean isElectedLeader = false;
+ private volatile boolean isActivatedLeader = false;
+
+ // For routes announced by local BGP daemon in SDN network,
+ // the next hop will be 0.0.0.0.
+ public static final IpAddress LOCAL_NEXT_HOP = IpAddress.valueOf("0.0.0.0");
+
+ /**
+ * Class constructor.
+ *
+ * @param intentService the intent service
+ * @param proxyArp the proxy ARP service
+ * @param configInfoService the configuration service
+ * @param interfaceService the interface service
+ */
+ public Router(IntentService intentService, HostService hostService,
+ SdnIpConfigService configInfoService, InterfaceService interfaceService) {
+
+ this.intentService = intentService;
+ this.hostService = hostService;
+ this.configInfoService = configInfoService;
+ this.interfaceService = interfaceService;
+
+ bgpRoutes = new ConcurrentInvertedRadixTree<>(
+ new DefaultByteArrayNodeFactory());
+ routeUpdates = new LinkedBlockingQueue<>();
+ routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
+ HashMultimap.<IpAddress, RouteEntry>create());
+ pushedRouteIntents = new ConcurrentHashMap<>();
+
+ bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
+ new ThreadFactoryBuilder().setNameFormat("bgp-updates-%d").build());
+ bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
+ new ThreadFactoryBuilder()
+ .setNameFormat("bgp-intents-synchronizer-%d").build());
+
+ this.hostService.addListener(new InternalHostListener());
+ }
+
+ /**
+ * Starts the Router.
+ */
+ public void start() {
+
+ bgpUpdatesExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ doUpdatesThread();
+ }
+ });
+
+ bgpIntentsSynchronizerExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ doIntentSynchronizationThread();
+ }
+ });
+ }
+
+ //@Override TODO hook this up to something
+ public void leaderChanged(boolean isLeader) {
+ log.debug("Leader changed: {}", isLeader);
+
+ if (!isLeader) {
+ this.isElectedLeader = false;
+ this.isActivatedLeader = false;
+ return; // Nothing to do
+ }
+ this.isActivatedLeader = false;
+ this.isElectedLeader = true;
+
+ //
+ // Tell the Intents Synchronizer thread to start the synchronization
+ //
+ intentsSynchronizerSemaphore.release();
+ }
+
+ @Override
+ public void update(RouteUpdate routeUpdate) {
+ log.debug("Received new route Update: {}", routeUpdate);
+
+ try {
+ routeUpdates.put(routeUpdate);
+ } catch (InterruptedException e) {
+ log.debug("Interrupted while putting on routeUpdates queue", e);
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Thread for Intent Synchronization.
+ */
+ private void doIntentSynchronizationThread() {
+ boolean interrupted = false;
+ try {
+ while (!interrupted) {
+ try {
+ intentsSynchronizerSemaphore.acquire();
+ //
+ // Drain all permits, because a single synchronization is
+ // sufficient.
+ //
+ intentsSynchronizerSemaphore.drainPermits();
+ } catch (InterruptedException e) {
+ log.debug("Interrupted while waiting to become " +
+ "Intent Synchronization leader");
+ interrupted = true;
+ break;
+ }
+ syncIntents();
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Thread for handling route updates.
+ */
+ private void doUpdatesThread() {
+ boolean interrupted = false;
+ try {
+ while (!interrupted) {
+ try {
+ RouteUpdate update = routeUpdates.take();
+ switch (update.type()) {
+ case UPDATE:
+ processRouteAdd(update.routeEntry());
+ break;
+ case DELETE:
+ processRouteDelete(update.routeEntry());
+ break;
+ default:
+ log.error("Unknown update Type: {}", update.type());
+ break;
+ }
+ } catch (InterruptedException e) {
+ log.debug("Interrupted while taking from updates queue", e);
+ interrupted = true;
+ } catch (Exception e) {
+ log.debug("exception", e);
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Performs Intents Synchronization between the internally stored Route
+ * Intents and the installed Route Intents.
+ */
+ private void syncIntents() {
+ synchronized (this) {
+ if (!isElectedLeader) {
+ return; // Nothing to do: not the leader anymore
+ }
+ log.debug("Syncing SDN-IP Route Intents...");
+
+ Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents =
+ new HashMap<>();
+
+ //
+ // Fetch all intents, and classify the Multi-Point-to-Point Intents
+ // based on the matching prefix.
+ //
+ for (Intent intent : intentService.getIntents()) {
+ //
+ // TODO: Ignore all intents that are not installed by
+ // the SDN-IP application.
+ //
+ if (!(intent instanceof MultiPointToSinglePointIntent)) {
+ continue;
+ }
+ MultiPointToSinglePointIntent mp2pIntent =
+ (MultiPointToSinglePointIntent) intent;
+ /*Match match = mp2pIntent.getMatch();
+ if (!(match instanceof PacketMatch)) {
+ continue;
+ }
+ PacketMatch packetMatch = (PacketMatch) match;
+ Ip4Prefix prefix = packetMatch.getDstIpAddress();
+ if (prefix == null) {
+ continue;
+ }
+ fetchedIntents.put(prefix, mp2pIntent);*/
+ for (Criterion criterion : mp2pIntent.selector().criteria()) {
+ if (criterion.type() == Type.IPV4_DST) {
+ IPCriterion ipCriterion = (IPCriterion) criterion;
+ fetchedIntents.put(ipCriterion.ip(), mp2pIntent);
+ }
+ }
+
+ }
+
+ //
+ // Compare for each prefix the local IN-MEMORY Intents with the
+ // FETCHED Intents:
+ // - If the IN-MEMORY Intent is same as the FETCHED Intent, store
+ // the FETCHED Intent in the local memory (i.e., override the
+ // IN-MEMORY Intent) to preserve the original Intent ID
+ // - if the IN-MEMORY Intent is not same as the FETCHED Intent,
+ // delete the FETCHED Intent, and push/install the IN-MEMORY
+ // Intent.
+ // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
+ // Intent for same prefix, then push/install the IN-MEMORY
+ // Intent.
+ // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
+ // Intent for same prefix, then delete/withdraw the FETCHED
+ // Intent.
+ //
+ Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
+ storeInMemoryIntents = new LinkedList<>();
+ Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
+ addIntents = new LinkedList<>();
+ Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
+ deleteIntents = new LinkedList<>();
+ for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
+ pushedRouteIntents.entrySet()) {
+ IpPrefix prefix = entry.getKey();
+ MultiPointToSinglePointIntent inMemoryIntent =
+ entry.getValue();
+ MultiPointToSinglePointIntent fetchedIntent =
+ fetchedIntents.get(prefix);
+
+ if (fetchedIntent == null) {
+ //
+ // No FETCHED Intent for same prefix: push the IN-MEMORY
+ // Intent.
+ //
+ addIntents.add(Pair.of(prefix, inMemoryIntent));
+ continue;
+ }
+
+ //
+ // If IN-MEMORY Intent is same as the FETCHED Intent,
+ // store the FETCHED Intent in the local memory.
+ //
+ if (compareMultiPointToSinglePointIntents(inMemoryIntent,
+ fetchedIntent)) {
+ storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent));
+ } else {
+ //
+ // The IN-MEMORY Intent is not same as the FETCHED Intent,
+ // hence delete the FETCHED Intent, and install the
+ // IN-MEMORY Intent.
+ //
+ deleteIntents.add(Pair.of(prefix, fetchedIntent));
+ addIntents.add(Pair.of(prefix, inMemoryIntent));
+ }
+ fetchedIntents.remove(prefix);
+ }
+
+ //
+ // Any remaining FETCHED Intents have to be deleted/withdrawn
+ //
+ for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
+ fetchedIntents.entrySet()) {
+ IpPrefix prefix = entry.getKey();
+ MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
+ deleteIntents.add(Pair.of(prefix, fetchedIntent));
+ }
+
+ //
+ // Perform the actions:
+ // 1. Store in memory fetched intents that are same. Can be done
+ // even if we are not the leader anymore
+ // 2. Delete intents: check if the leader before each operation
+ // 3. Add intents: check if the leader before each operation
+ //
+ for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
+ storeInMemoryIntents) {
+ IpPrefix prefix = pair.getLeft();
+ MultiPointToSinglePointIntent intent = pair.getRight();
+ log.debug("Intent synchronization: updating in-memory " +
+ "Intent for prefix: {}", prefix);
+ pushedRouteIntents.put(prefix, intent);
+ }
+ //
+ isActivatedLeader = true; // Allow push of Intents
+ for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
+ deleteIntents) {
+ IpPrefix prefix = pair.getLeft();
+ MultiPointToSinglePointIntent intent = pair.getRight();
+ if (!isElectedLeader) {
+ isActivatedLeader = false;
+ return;
+ }
+ log.debug("Intent synchronization: deleting Intent for " +
+ "prefix: {}", prefix);
+ intentService.withdraw(intent);
+ }
+ //
+ for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
+ addIntents) {
+ IpPrefix prefix = pair.getLeft();
+ MultiPointToSinglePointIntent intent = pair.getRight();
+ if (!isElectedLeader) {
+ isActivatedLeader = false;
+ return;
+ }
+ log.debug("Intent synchronization: adding Intent for " +
+ "prefix: {}", prefix);
+ intentService.submit(intent);
+ }
+ if (!isElectedLeader) {
+ isActivatedLeader = false;
+ }
+ log.debug("Syncing SDN-IP routes completed.");
+ }
+ }
+
+ /**
+ * Compares two Multi-point to Single Point Intents whether they represent
+ * same logical intention.
+ *
+ * @param intent1 the first Intent to compare
+ * @param intent2 the second Intent to compare
+ * @return true if both Intents represent same logical intention, otherwise
+ * false
+ */
+ private boolean compareMultiPointToSinglePointIntents(
+ MultiPointToSinglePointIntent intent1,
+ MultiPointToSinglePointIntent intent2) {
+ /*Match match1 = intent1.getMatch();
+ Match match2 = intent2.getMatch();
+ Action action1 = intent1.getAction();
+ Action action2 = intent2.getAction();
+ Set<SwitchPort> ingressPorts1 = intent1.getIngressPorts();
+ Set<SwitchPort> ingressPorts2 = intent2.getIngressPorts();
+ SwitchPort egressPort1 = intent1.getEgressPort();
+ SwitchPort egressPort2 = intent2.getEgressPort();
+
+ return Objects.equal(match1, match2) &&
+ Objects.equal(action1, action2) &&
+ Objects.equal(egressPort1, egressPort2) &&
+ Objects.equal(ingressPorts1, ingressPorts2);*/
+ return Objects.equal(intent1.selector(), intent2.selector()) &&
+ Objects.equal(intent1.treatment(), intent2.treatment()) &&
+ Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
+ Objects.equal(intent1.egressPoint(), intent2.egressPoint());
+ }
+
+ /**
+ * Processes adding a route entry.
+ * <p/>
+ * Put new route entry into InvertedRadixTree. If there was an existing
+ * nexthop for this prefix, but the next hop was different, then execute
+ * deleting old route entry. If the next hop is the SDN domain, we do not
+ * handle it at the moment. Otherwise, execute adding a route.
+ *
+ * @param routeEntry the route entry to add
+ */
+ protected void processRouteAdd(RouteEntry routeEntry) {
+ synchronized (this) {
+ log.debug("Processing route add: {}", routeEntry);
+
+ IpPrefix prefix = routeEntry.prefix();
+ IpAddress nextHop = null;
+ RouteEntry foundRouteEntry =
+ bgpRoutes.put(RouteEntry.createBinaryString(prefix),
+ routeEntry);
+ if (foundRouteEntry != null) {
+ nextHop = foundRouteEntry.nextHop();
+ }
+
+ if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) {
+ // There was an existing nexthop for this prefix. This update
+ // supersedes that, so we need to remove the old flows for this
+ // prefix from the switches
+ executeRouteDelete(routeEntry);
+ }
+ if (nextHop != null && nextHop.equals(routeEntry.nextHop())) {
+ return;
+ }
+
+ if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) {
+ // Route originated by SDN domain
+ // We don't handle these at the moment
+ log.debug("Own route {} to {}",
+ routeEntry.prefix(), routeEntry.nextHop());
+ return;
+ }
+
+ executeRouteAdd(routeEntry);
+ }
+ }
+
+ /**
+ * Executes adding a route entry.
+ * <p/>
+ * Find out the egress Interface and MAC address of next hop router for
+ * this route entry. If the MAC address can not be found in ARP cache,
+ * then this prefix will be put in routesWaitingOnArp queue. Otherwise,
+ * new route intent will be created and installed.
+ *
+ * @param routeEntry the route entry to add
+ */
+ private void executeRouteAdd(RouteEntry routeEntry) {
+ log.debug("Executing route add: {}", routeEntry);
+
+ // See if we know the MAC address of the next hop
+ //MacAddress nextHopMacAddress =
+ //proxyArp.getMacAddress(routeEntry.getNextHop());
+ MacAddress nextHopMacAddress = null;
+ Set<Host> hosts = hostService.getHostsByIp(
+ routeEntry.nextHop().toPrefix());
+ if (!hosts.isEmpty()) {
+ // TODO how to handle if multiple hosts are returned?
+ nextHopMacAddress = hosts.iterator().next().mac();
+ }
+
+ if (nextHopMacAddress == null) {
+ routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
+ //proxyArp.sendArpRequest(routeEntry.getNextHop(), this, true);
+ // TODO maybe just do this for every prefix anyway
+ hostService.startMonitoringIp(routeEntry.nextHop());
+ return;
+ }
+
+ addRouteIntentToNextHop(routeEntry.prefix(),
+ routeEntry.nextHop(),
+ nextHopMacAddress);
+ }
+
+ /**
+ * Adds a route intent given a prefix and a next hop IP address. This
+ * method will find the egress interface for the intent.
+ *
+ * @param prefix IP prefix of the route to add
+ * @param nextHopIpAddress IP address of the next hop
+ * @param nextHopMacAddress MAC address of the next hop
+ */
+ private void addRouteIntentToNextHop(IpPrefix prefix,
+ IpAddress nextHopIpAddress,
+ MacAddress nextHopMacAddress) {
+
+ // Find the attachment point (egress interface) of the next hop
+ Interface egressInterface;
+ if (configInfoService.getBgpPeers().containsKey(nextHopIpAddress)) {
+ // Route to a peer
+ log.debug("Route to peer {}", nextHopIpAddress);
+ BgpPeer peer =
+ configInfoService.getBgpPeers().get(nextHopIpAddress);
+ egressInterface =
+ interfaceService.getInterface(peer.connectPoint());
+ } else {
+ // Route to non-peer
+ log.debug("Route to non-peer {}", nextHopIpAddress);
+ egressInterface =
+ interfaceService.getMatchingInterface(nextHopIpAddress);
+ if (egressInterface == null) {
+ log.warn("No outgoing interface found for {}",
+ nextHopIpAddress);
+ return;
+ }
+ }
+
+ doAddRouteIntent(prefix, egressInterface, nextHopMacAddress);
+ }
+
+ /**
+ * Installs a route intent for a prefix.
+ * <p/>
+ * Intent will match dst IP prefix and rewrite dst MAC address at all other
+ * border switches, then forward packets according to dst MAC address.
+ *
+ * @param prefix IP prefix from route
+ * @param egressInterface egress Interface connected to next hop router
+ * @param nextHopMacAddress MAC address of next hop router
+ */
+ private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface,
+ MacAddress nextHopMacAddress) {
+ log.debug("Adding intent for prefix {}, next hop mac {}",
+ prefix, nextHopMacAddress);
+
+ MultiPointToSinglePointIntent pushedIntent =
+ pushedRouteIntents.get(prefix);
+
+ // Just for testing.
+ if (pushedIntent != null) {
+ log.error("There should not be a pushed intent: {}", pushedIntent);
+ }
+
+ ConnectPoint egressPort = egressInterface.connectPoint();
+
+ Set<ConnectPoint> ingressPorts = new HashSet<>();
+
+ for (Interface intf : interfaceService.getInterfaces()) {
+ if (!intf.equals(egressInterface)) {
+ ConnectPoint srcPort = intf.connectPoint();
+ ingressPorts.add(srcPort);
+ }
+ }
+
+ // Match the destination IP prefix at the first hop
+ //PacketMatchBuilder builder = new PacketMatchBuilder();
+ //builder.setEtherType(Ethernet.TYPE_IPV4).setDstIpNet(prefix);
+ //PacketMatch packetMatch = builder.build();
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(prefix)
+ .build();
+
+ // Rewrite the destination MAC address
+ //ModifyDstMacAction modifyDstMacAction =
+ //new ModifyDstMacAction(nextHopMacAddress);
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setEthDst(nextHopMacAddress)
+ .build();
+
+ MultiPointToSinglePointIntent intent =
+ new MultiPointToSinglePointIntent(nextIntentId(),
+ selector, treatment, ingressPorts, egressPort);
+
+ if (isElectedLeader && isActivatedLeader) {
+ log.debug("Intent installation: adding Intent for prefix: {}",
+ prefix);
+ intentService.submit(intent);
+ }
+
+ // Maintain the Intent
+ pushedRouteIntents.put(prefix, intent);
+ }
+
+ /**
+ * Executes deleting a route entry.
+ * <p/>
+ * Removes prefix from InvertedRadixTree, if success, then try to delete
+ * the relative intent.
+ *
+ * @param routeEntry the route entry to delete
+ */
+ protected void processRouteDelete(RouteEntry routeEntry) {
+ synchronized (this) {
+ log.debug("Processing route delete: {}", routeEntry);
+ IpPrefix prefix = routeEntry.prefix();
+
+ // TODO check the change of logic here - remove doesn't check that
+ // the route entry was what we expected (and we can't do this
+ // concurrently)
+
+ if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) {
+ //
+ // Only delete flows if an entry was actually removed from the
+ // tree. If no entry was removed, the <prefix, nexthop> wasn't
+ // there so it's probably already been removed and we don't
+ // need to do anything.
+ //
+ executeRouteDelete(routeEntry);
+ }
+
+ routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
+ // TODO cancel the request in the ARP manager as well
+ }
+ }
+
+ /**
+ * Executed deleting a route entry.
+ *
+ * @param routeEntry the route entry to delete
+ */
+ private void executeRouteDelete(RouteEntry routeEntry) {
+ log.debug("Executing route delete: {}", routeEntry);
+
+ IpPrefix prefix = routeEntry.prefix();
+
+ MultiPointToSinglePointIntent intent =
+ pushedRouteIntents.remove(prefix);
+
+ if (intent == null) {
+ log.debug("There is no intent in pushedRouteIntents to delete " +
+ "for prefix: {}", prefix);
+ } else {
+ if (isElectedLeader && isActivatedLeader) {
+ log.debug("Intent installation: deleting Intent for prefix: {}",
+ prefix);
+ intentService.withdraw(intent);
+ }
+ }
+ }
+
+ /**
+ * This method handles the prefixes which are waiting for ARP replies for
+ * MAC addresses of next hops.
+ *
+ * @param ipAddress next hop router IP address, for which we sent ARP
+ * request out
+ * @param macAddress MAC address which is relative to the ipAddress
+ */
+ //@Override
+ // TODO change name
+ public void arpResponse(IpAddress ipAddress, MacAddress macAddress) {
+ log.debug("Received ARP response: {} => {}", ipAddress, macAddress);
+
+ // We synchronize on this to prevent changes to the InvertedRadixTree
+ // while we're pushing intent. If the InvertedRadixTree changes, the
+ // InvertedRadixTree and intent could get out of sync.
+ synchronized (this) {
+
+ Set<RouteEntry> routesToPush =
+ routesWaitingOnArp.removeAll(ipAddress);
+
+ for (RouteEntry routeEntry : routesToPush) {
+ // These will always be adds
+ IpPrefix prefix = routeEntry.prefix();
+ String binaryString = RouteEntry.createBinaryString(prefix);
+ RouteEntry foundRouteEntry =
+ bgpRoutes.getValueForExactKey(binaryString);
+ if (foundRouteEntry != null &&
+ foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
+ log.debug("Pushing prefix {} next hop {}",
+ routeEntry.prefix(), routeEntry.nextHop());
+ // We only push prefix flows if the prefix is still in the
+ // InvertedRadixTree and the next hop is the same as our
+ // update.
+ // The prefix could have been removed while we were waiting
+ // for the ARP, or the next hop could have changed.
+ addRouteIntentToNextHop(prefix, ipAddress, macAddress);
+ } else {
+ log.debug("Received ARP response, but {}/{} is no longer in"
+ + " InvertedRadixTree", routeEntry.prefix(),
+ routeEntry.nextHop());
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the SDN-IP routes.
+ *
+ * @return the SDN-IP routes
+ */
+ public Collection<RouteEntry> getRoutes() {
+ Iterator<KeyValuePair<RouteEntry>> it =
+ bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator();
+
+ List<RouteEntry> routes = new LinkedList<>();
+
+ while (it.hasNext()) {
+ KeyValuePair<RouteEntry> entry = it.next();
+ routes.add(entry.getValue());
+ }
+
+ return routes;
+ }
+
+ /**
+ * Generates a new unique intent ID.
+ *
+ * @return the new intent ID.
+ */
+ private IntentId nextIntentId() {
+ return new IntentId(intentId++);
+ }
+
+ /**
+ * Listener for host events.
+ */
+ class InternalHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ if (event.type() == HostEvent.Type.HOST_ADDED ||
+ event.type() == HostEvent.Type.HOST_UPDATED) {
+ Host host = event.subject();
+ for (IpPrefix ip : host.ipAddresses()) {
+ arpResponse(ip.toIpAddress(), host.mac());
+ }
+ }
+ }
+ }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
index 25b13f1..3f94cba 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
@@ -9,7 +9,11 @@
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.sdnip.RouteUpdate.Type;
+import org.onlab.onos.sdnip.bgp.BgpSessionManager;
import org.onlab.onos.sdnip.config.SdnIpConfigReader;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
import org.slf4j.Logger;
/**
@@ -28,6 +32,8 @@
private SdnIpConfigReader config;
private PeerConnectivity peerConnectivity;
+ private Router router;
+ private BgpSessionManager bgpSessionManager;
@Activate
protected void activate() {
@@ -41,6 +47,17 @@
peerConnectivity = new PeerConnectivity(config, interfaceService, intentService);
peerConnectivity.start();
+ router = new Router(intentService, hostService, config, interfaceService);
+ router.start();
+
+ bgpSessionManager = new BgpSessionManager(router);
+ bgpSessionManager.startUp(2000); // TODO
+
+ // TODO need to disable link discovery on external ports
+
+ router.update(new RouteUpdate(Type.UPDATE, new RouteEntry(
+ IpPrefix.valueOf("172.16.20.0/24"),
+ IpAddress.valueOf("192.168.10.1"))));
}
@Deactivate
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java
new file mode 100644
index 0000000..1703138
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java
@@ -0,0 +1,368 @@
+package org.onlab.onos.sdnip.bgp;
+
+/**
+ * BGP related constants.
+ */
+public final class BgpConstants {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private BgpConstants() {
+ }
+
+ /** BGP port number (RFC 4271). */
+ public static final int BGP_PORT = 179;
+
+ /** BGP version. */
+ public static final int BGP_VERSION = 4;
+
+ /** BGP OPEN message type. */
+ public static final int BGP_TYPE_OPEN = 1;
+
+ /** BGP UPDATE message type. */
+ public static final int BGP_TYPE_UPDATE = 2;
+
+ /** BGP NOTIFICATION message type. */
+ public static final int BGP_TYPE_NOTIFICATION = 3;
+
+ /** BGP KEEPALIVE message type. */
+ public static final int BGP_TYPE_KEEPALIVE = 4;
+
+ /** BGP Header Marker field length. */
+ public static final int BGP_HEADER_MARKER_LENGTH = 16;
+
+ /** BGP Header length. */
+ public static final int BGP_HEADER_LENGTH = 19;
+
+ /** BGP message maximum length. */
+ public static final int BGP_MESSAGE_MAX_LENGTH = 4096;
+
+ /** BGP OPEN message minimum length (BGP Header included). */
+ public static final int BGP_OPEN_MIN_LENGTH = 29;
+
+ /** BGP UPDATE message minimum length (BGP Header included). */
+ public static final int BGP_UPDATE_MIN_LENGTH = 23;
+
+ /** BGP NOTIFICATION message minimum length (BGP Header included). */
+ public static final int BGP_NOTIFICATION_MIN_LENGTH = 21;
+
+ /** BGP KEEPALIVE message expected length (BGP Header included). */
+ public static final int BGP_KEEPALIVE_EXPECTED_LENGTH = 19;
+
+ /** BGP KEEPALIVE messages transmitted per Hold interval. */
+ public static final int BGP_KEEPALIVE_PER_HOLD_INTERVAL = 3;
+
+ /** BGP KEEPALIVE messages minimum Holdtime (in seconds). */
+ public static final int BGP_KEEPALIVE_MIN_HOLDTIME = 3;
+
+ /** BGP KEEPALIVE messages minimum transmission interval (in seconds). */
+ public static final int BGP_KEEPALIVE_MIN_INTERVAL = 1;
+
+ /** BGP AS 0 (zero) value. See draft-ietf-idr-as0-06.txt Internet Draft. */
+ public static final long BGP_AS_0 = 0;
+
+ /**
+ * BGP UPDATE related constants.
+ */
+ public static final class Update {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private Update() {
+ }
+
+ /**
+ * BGP UPDATE: ORIGIN related constants.
+ */
+ public static final class Origin {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private Origin() {
+ }
+
+ /** BGP UPDATE Attributes Type Code ORIGIN. */
+ public static final int TYPE = 1;
+
+ /** BGP UPDATE Attributes Type Code ORIGIN length. */
+ public static final int LENGTH = 1;
+
+ /** BGP UPDATE ORIGIN: IGP. */
+ public static final int IGP = 0;
+
+ /** BGP UPDATE ORIGIN: EGP. */
+ public static final int EGP = 1;
+
+ /** BGP UPDATE ORIGIN: INCOMPLETE. */
+ public static final int INCOMPLETE = 2;
+ }
+
+ /**
+ * BGP UPDATE: AS_PATH related constants.
+ */
+ public static final class AsPath {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private AsPath() {
+ }
+
+ /** BGP UPDATE Attributes Type Code AS_PATH. */
+ public static final int TYPE = 2;
+
+ /** BGP UPDATE AS_PATH Type: AS_SET. */
+ public static final int AS_SET = 1;
+
+ /** BGP UPDATE AS_PATH Type: AS_SEQUENCE. */
+ public static final int AS_SEQUENCE = 2;
+ }
+
+ /**
+ * BGP UPDATE: NEXT_HOP related constants.
+ */
+ public static final class NextHop {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private NextHop() {
+ }
+
+ /** BGP UPDATE Attributes Type Code NEXT_HOP. */
+ public static final int TYPE = 3;
+
+ /** BGP UPDATE Attributes Type Code NEXT_HOP length. */
+ public static final int LENGTH = 4;
+ }
+
+ /**
+ * BGP UPDATE: MULTI_EXIT_DISC related constants.
+ */
+ public static final class MultiExitDisc {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private MultiExitDisc() {
+ }
+
+ /** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC. */
+ public static final int TYPE = 4;
+
+ /** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC length. */
+ public static final int LENGTH = 4;
+
+ /** BGP UPDATE Attributes lowest MULTI_EXIT_DISC value. */
+ public static final int LOWEST_MULTI_EXIT_DISC = 0;
+ }
+
+ /**
+ * BGP UPDATE: LOCAL_PREF related constants.
+ */
+ public static final class LocalPref {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private LocalPref() {
+ }
+
+ /** BGP UPDATE Attributes Type Code LOCAL_PREF. */
+ public static final int TYPE = 5;
+
+ /** BGP UPDATE Attributes Type Code LOCAL_PREF length. */
+ public static final int LENGTH = 4;
+ }
+
+ /**
+ * BGP UPDATE: ATOMIC_AGGREGATE related constants.
+ */
+ public static final class AtomicAggregate {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private AtomicAggregate() {
+ }
+
+ /** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE. */
+ public static final int TYPE = 6;
+
+ /** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE length. */
+ public static final int LENGTH = 0;
+ }
+
+ /**
+ * BGP UPDATE: AGGREGATOR related constants.
+ */
+ public static final class Aggregator {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private Aggregator() {
+ }
+
+ /** BGP UPDATE Attributes Type Code AGGREGATOR. */
+ public static final int TYPE = 7;
+
+ /** BGP UPDATE Attributes Type Code AGGREGATOR length. */
+ public static final int LENGTH = 6;
+ }
+ }
+
+ /**
+ * BGP NOTIFICATION related constants.
+ */
+ public static final class Notifications {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private Notifications() {
+ }
+
+ /**
+ * BGP NOTIFICATION: Message Header Error constants.
+ */
+ public static final class MessageHeaderError {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private MessageHeaderError() {
+ }
+
+ /** Message Header Error code. */
+ public static final int ERROR_CODE = 1;
+
+ /** Message Header Error subcode: Connection Not Synchronized. */
+ public static final int CONNECTION_NOT_SYNCHRONIZED = 1;
+
+ /** Message Header Error subcode: Bad Message Length. */
+ public static final int BAD_MESSAGE_LENGTH = 2;
+
+ /** Message Header Error subcode: Bad Message Type. */
+ public static final int BAD_MESSAGE_TYPE = 3;
+ }
+
+ /**
+ * BGP NOTIFICATION: OPEN Message Error constants.
+ */
+ public static final class OpenMessageError {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private OpenMessageError() {
+ }
+
+ /** OPEN Message Error code. */
+ public static final int ERROR_CODE = 2;
+
+ /** OPEN Message Error subcode: Unsupported Version Number. */
+ public static final int UNSUPPORTED_VERSION_NUMBER = 1;
+
+ /** OPEN Message Error subcode: Bad PEER AS. */
+ public static final int BAD_PEER_AS = 2;
+
+ /** OPEN Message Error subcode: Unacceptable Hold Time. */
+ public static final int UNACCEPTABLE_HOLD_TIME = 6;
+ }
+
+ /**
+ * BGP NOTIFICATION: UPDATE Message Error constants.
+ */
+ public static final class UpdateMessageError {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private UpdateMessageError() {
+ }
+
+ /** UPDATE Message Error code. */
+ public static final int ERROR_CODE = 3;
+
+ /** UPDATE Message Error subcode: Malformed Attribute List. */
+ public static final int MALFORMED_ATTRIBUTE_LIST = 1;
+
+ /** UPDATE Message Error subcode: Unrecognized Well-known Attribute. */
+ public static final int UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE = 2;
+
+ /** UPDATE Message Error subcode: Missing Well-known Attribute. */
+ public static final int MISSING_WELL_KNOWN_ATTRIBUTE = 3;
+
+ /** UPDATE Message Error subcode: Attribute Flags Error. */
+ public static final int ATTRIBUTE_FLAGS_ERROR = 4;
+
+ /** UPDATE Message Error subcode: Attribute Length Error. */
+ public static final int ATTRIBUTE_LENGTH_ERROR = 5;
+
+ /** UPDATE Message Error subcode: Invalid ORIGIN Attribute. */
+ public static final int INVALID_ORIGIN_ATTRIBUTE = 6;
+
+ /** UPDATE Message Error subcode: Invalid NEXT_HOP Attribute. */
+ public static final int INVALID_NEXT_HOP_ATTRIBUTE = 8;
+
+ /** UPDATE Message Error subcode: Optional Attribute Error. Unused. */
+ public static final int OPTIONAL_ATTRIBUTE_ERROR = 9;
+
+ /** UPDATE Message Error subcode: Invalid Network Field. */
+ public static final int INVALID_NETWORK_FIELD = 10;
+
+ /** UPDATE Message Error subcode: Malformed AS_PATH. */
+ public static final int MALFORMED_AS_PATH = 11;
+ }
+
+ /**
+ * BGP NOTIFICATION: Hold Timer Expired constants.
+ */
+ public static final class HoldTimerExpired {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private HoldTimerExpired() {
+ }
+
+ /** Hold Timer Expired code. */
+ public static final int ERROR_CODE = 4;
+ }
+
+ /** BGP NOTIFICATION message Error subcode: Unspecific. */
+ public static final int ERROR_SUBCODE_UNSPECIFIC = 0;
+ }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpFrameDecoder.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpFrameDecoder.java
new file mode 100644
index 0000000..938d975
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpFrameDecoder.java
@@ -0,0 +1,162 @@
+package org.onlab.onos.sdnip.bgp;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class for handling the decoding of the BGP messages.
+ */
+class BgpFrameDecoder extends FrameDecoder {
+ private static final Logger log =
+ LoggerFactory.getLogger(BgpFrameDecoder.class);
+
+ private final BgpSession bgpSession;
+
+ /**
+ * Constructor for a given BGP Session.
+ *
+ * @param bgpSession the BGP session state to use.
+ */
+ BgpFrameDecoder(BgpSession bgpSession) {
+ this.bgpSession = bgpSession;
+ }
+
+ @Override
+ protected Object decode(ChannelHandlerContext ctx,
+ Channel channel,
+ ChannelBuffer buf) throws Exception {
+ //
+ // NOTE: If we close the channel during the decoding, we might still
+ // see some incoming messages while the channel closing is completed.
+ //
+ if (bgpSession.isClosed()) {
+ return null;
+ }
+
+ log.trace("BGP Peer: decode(): remoteAddr = {} localAddr = {} " +
+ "messageSize = {}",
+ ctx.getChannel().getRemoteAddress(),
+ ctx.getChannel().getLocalAddress(),
+ buf.readableBytes());
+
+ // Test for minimum length of the BGP message
+ if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) {
+ // No enough data received
+ return null;
+ }
+
+ //
+ // Mark the current buffer position in case we haven't received
+ // the whole message.
+ //
+ buf.markReaderIndex();
+
+ //
+ // Read and check the BGP message Marker field: it must be all ones
+ // (See RFC 4271, Section 4.1)
+ //
+ byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH];
+ buf.readBytes(marker);
+ for (int i = 0; i < marker.length; i++) {
+ if (marker[i] != (byte) 0xff) {
+ log.debug("BGP RX Error: invalid marker {} at position {}",
+ marker[i], i);
+ //
+ // ERROR: Connection Not Synchronized
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = MessageHeaderError.ERROR_CODE;
+ int errorSubcode =
+ MessageHeaderError.CONNECTION_NOT_SYNCHRONIZED;
+ ChannelBuffer txMessage =
+ bgpSession.prepareBgpNotification(errorCode, errorSubcode,
+ null);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeChannel(ctx);
+ return null;
+ }
+ }
+
+ //
+ // Read and check the BGP message Length field
+ //
+ int length = buf.readUnsignedShort();
+ if ((length < BgpConstants.BGP_HEADER_LENGTH) ||
+ (length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) {
+ log.debug("BGP RX Error: invalid Length field {}. " +
+ "Must be between {} and {}",
+ length,
+ BgpConstants.BGP_HEADER_LENGTH,
+ BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+ //
+ // ERROR: Bad Message Length
+ //
+ // Send NOTIFICATION and close the connection
+ ChannelBuffer txMessage =
+ bgpSession.prepareBgpNotificationBadMessageLength(length);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeChannel(ctx);
+ return null;
+ }
+
+ //
+ // Test whether the rest of the message is received:
+ // So far we have read the Marker (16 octets) and the
+ // Length (2 octets) fields.
+ //
+ int remainingMessageLen =
+ length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2;
+ if (buf.readableBytes() < remainingMessageLen) {
+ // No enough data received
+ buf.resetReaderIndex();
+ return null;
+ }
+
+ //
+ // Read the BGP message Type field, and process based on that type
+ //
+ int type = buf.readUnsignedByte();
+ remainingMessageLen--; // Adjust after reading the type
+ ChannelBuffer message = buf.readBytes(remainingMessageLen);
+
+ //
+ // Process the remaining of the message based on the message type
+ //
+ switch (type) {
+ case BgpConstants.BGP_TYPE_OPEN:
+ bgpSession.processBgpOpen(ctx, message);
+ break;
+ case BgpConstants.BGP_TYPE_UPDATE:
+ bgpSession.processBgpUpdate(ctx, message);
+ break;
+ case BgpConstants.BGP_TYPE_NOTIFICATION:
+ bgpSession.processBgpNotification(ctx, message);
+ break;
+ case BgpConstants.BGP_TYPE_KEEPALIVE:
+ bgpSession.processBgpKeepalive(ctx, message);
+ break;
+ default:
+ //
+ // ERROR: Bad Message Type
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = MessageHeaderError.ERROR_CODE;
+ int errorSubcode = MessageHeaderError.BAD_MESSAGE_TYPE;
+ ChannelBuffer data = ChannelBuffers.buffer(1);
+ data.writeByte(type);
+ ChannelBuffer txMessage =
+ bgpSession.prepareBgpNotification(errorCode, errorSubcode,
+ data);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeChannel(ctx);
+ return null;
+ }
+ return null;
+ }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
new file mode 100644
index 0000000..890328a
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
@@ -0,0 +1,432 @@
+package org.onlab.onos.sdnip.bgp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import org.onlab.onos.sdnip.RouteEntry;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Represents a route in BGP.
+ */
+public class BgpRouteEntry extends RouteEntry {
+ private final BgpSession bgpSession; // The BGP Session the route was
+ // received on
+ private final byte origin; // Route ORIGIN: IGP, EGP, INCOMPLETE
+ private final AsPath asPath; // The AS Path
+ private final long localPref; // The local preference for the route
+ private long multiExitDisc =
+ BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
+
+ /**
+ * Class constructor.
+ *
+ * @param bgpSession the BGP Session the route was received on
+ * @param prefix the prefix of the route
+ * @param nextHop the next hop of the route
+ * @param origin the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
+ * @param asPath the AS path
+ * @param localPref the route local preference
+ */
+ public BgpRouteEntry(BgpSession bgpSession, IpPrefix prefix,
+ IpAddress nextHop, byte origin,
+ BgpRouteEntry.AsPath asPath, long localPref) {
+ super(prefix, nextHop);
+ this.bgpSession = checkNotNull(bgpSession);
+ this.origin = origin;
+ this.asPath = checkNotNull(asPath);
+ this.localPref = localPref;
+ }
+
+ /**
+ * Gets the BGP Session the route was received on.
+ *
+ * @return the BGP Session the route was received on
+ */
+ public BgpSession getBgpSession() {
+ return bgpSession;
+ }
+
+ /**
+ * Gets the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE.
+ *
+ * @return the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
+ */
+ public byte getOrigin() {
+ return origin;
+ }
+
+ /**
+ * Gets the route AS path.
+ *
+ * @return the route AS path
+ */
+ public BgpRouteEntry.AsPath getAsPath() {
+ return asPath;
+ }
+
+ /**
+ * Gets the route local preference.
+ *
+ * @return the route local preference
+ */
+ public long getLocalPref() {
+ return localPref;
+ }
+
+ /**
+ * Gets the route MED (Multi-Exit Discriminator).
+ *
+ * @return the route MED (Multi-Exit Discriminator)
+ */
+ public long getMultiExitDisc() {
+ return multiExitDisc;
+ }
+
+ /**
+ * Sets the route MED (Multi-Exit Discriminator).
+ *
+ * @param multiExitDisc the route MED (Multi-Exit Discriminator) to set
+ */
+ void setMultiExitDisc(long multiExitDisc) {
+ this.multiExitDisc = multiExitDisc;
+ }
+
+ /**
+ * Tests whether the route is originated from the local AS.
+ * <p/>
+ * The route is considered originated from the local AS if the AS Path
+ * is empty or if it begins with an AS_SET.
+ *
+ * @return true if the route is originated from the local AS, otherwise
+ * false
+ */
+ boolean isLocalRoute() {
+ if (asPath.getPathSegments().isEmpty()) {
+ return true;
+ }
+ PathSegment firstPathSegment = asPath.getPathSegments().get(0);
+ if (firstPathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Gets the BGP Neighbor AS number the route was received from.
+ * <p/>
+ * If the router is originated from the local AS, the return value is
+ * zero (BGP_AS_0).
+ *
+ * @return the BGP Neighbor AS number the route was received from.
+ */
+ long getNeighborAs() {
+ if (isLocalRoute()) {
+ return BgpConstants.BGP_AS_0;
+ }
+ PathSegment firstPathSegment = asPath.getPathSegments().get(0);
+ if (firstPathSegment.getSegmentAsNumbers().isEmpty()) {
+ // TODO: Shouldn't happen. Should check during the parsing.
+ return BgpConstants.BGP_AS_0;
+ }
+ return firstPathSegment.getSegmentAsNumbers().get(0);
+ }
+
+ /**
+ * Tests whether the AS Path contains a loop.
+ * <p/>
+ * The test is done by comparing whether the AS Path contains the
+ * local AS number.
+ *
+ * @param localAsNumber the local AS number to compare against
+ * @return true if the AS Path contains a loop, otherwise false
+ */
+ boolean hasAsPathLoop(long localAsNumber) {
+ for (PathSegment pathSegment : asPath.getPathSegments()) {
+ for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
+ if (asNumber.equals(localAsNumber)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Compares this BGP route against another BGP route by using the
+ * BGP Decision Process.
+ * <p/>
+ * NOTE: The comparison needs to be performed only on routes that have
+ * same IP Prefix.
+ *
+ * @param other the BGP route to compare against
+ * @return true if this BGP route is better than the other BGP route
+ * or same, otherwise false
+ */
+ boolean isBetterThan(BgpRouteEntry other) {
+ if (this == other) {
+ return true; // Return true if same route
+ }
+
+ // Compare the LOCAL_PREF values: larger is better
+ if (getLocalPref() != other.getLocalPref()) {
+ return (getLocalPref() > other.getLocalPref());
+ }
+
+ // Compare the AS number in the path: smaller is better
+ if (getAsPath().getAsPathLength() !=
+ other.getAsPath().getAsPathLength()) {
+ return getAsPath().getAsPathLength() <
+ other.getAsPath().getAsPathLength();
+ }
+
+ // Compare the Origin number: lower is better
+ if (getOrigin() != other.getOrigin()) {
+ return (getOrigin() < other.getOrigin());
+ }
+
+ // Compare the MED if the neighbor AS is same: larger is better
+ medLabel: {
+ boolean thisIsLocalRoute = isLocalRoute();
+ if (thisIsLocalRoute != other.isLocalRoute()) {
+ break medLabel; // AS number is different
+ }
+ if (!thisIsLocalRoute) {
+ long thisNeighborAs = getNeighborAs();
+ if (thisNeighborAs != other.getNeighborAs()) {
+ break medLabel; // AS number is different
+ }
+ if (thisNeighborAs == BgpConstants.BGP_AS_0) {
+ break medLabel; // Invalid AS number
+ }
+ }
+
+ // Compare the MED
+ if (getMultiExitDisc() != other.getMultiExitDisc()) {
+ return (getMultiExitDisc() > other.getMultiExitDisc());
+ }
+ }
+
+ // Compare the peer BGP ID: lower is better
+ IpAddress peerBgpId = getBgpSession().getRemoteBgpId();
+ IpAddress otherPeerBgpId = other.getBgpSession().getRemoteBgpId();
+ if (!peerBgpId.equals(otherPeerBgpId)) {
+ return (peerBgpId.compareTo(otherPeerBgpId) < 0);
+ }
+
+ // Compare the peer BGP address: lower is better
+ IpAddress peerAddress = getBgpSession().getRemoteIp4Address();
+ IpAddress otherPeerAddress =
+ other.getBgpSession().getRemoteIp4Address();
+ if (!peerAddress.equals(otherPeerAddress)) {
+ return (peerAddress.compareTo(otherPeerAddress) < 0);
+ }
+
+ return true; // Routes are same. Shouldn't happen?
+ }
+
+ /**
+ * A class to represent AS Path Segment.
+ */
+ public static class PathSegment {
+ private final byte type; // Segment type: AS_SET, AS_SEQUENCE
+ private final ArrayList<Long> segmentAsNumbers; // Segment AS numbers
+
+ /**
+ * Constructor.
+ *
+ * @param type the Path Segment Type: 1=AS_SET, 2=AS_SEQUENCE
+ * @param segmentAsNumbers the Segment AS numbers
+ */
+ PathSegment(byte type, ArrayList<Long> segmentAsNumbers) {
+ this.type = type;
+ this.segmentAsNumbers = checkNotNull(segmentAsNumbers);
+ }
+
+ /**
+ * Gets the Path Segment Type: AS_SET, AS_SEQUENCE.
+ *
+ * @return the Path Segment Type: AS_SET, AS_SEQUENCE
+ */
+ public byte getType() {
+ return type;
+ }
+
+ /**
+ * Gets the Path Segment AS Numbers.
+ *
+ * @return the Path Segment AS Numbers
+ */
+ public ArrayList<Long> getSegmentAsNumbers() {
+ return segmentAsNumbers;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof PathSegment)) {
+ return false;
+ }
+
+ PathSegment otherPathSegment = (PathSegment) other;
+ return Objects.equals(this.type, otherPathSegment.type) &&
+ Objects.equals(this.segmentAsNumbers,
+ otherPathSegment.segmentAsNumbers);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, segmentAsNumbers);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("type", this.type)
+ .add("segmentAsNumbers", this.segmentAsNumbers)
+ .toString();
+ }
+ }
+
+ /**
+ * A class to represent AS Path.
+ */
+ public static class AsPath {
+ private final ArrayList<PathSegment> pathSegments;
+ private final int asPathLength; // Precomputed AS Path Length
+
+ /**
+ * Constructor.
+ *
+ * @param pathSegments the Path Segments of the Path
+ */
+ AsPath(ArrayList<PathSegment> pathSegments) {
+ this.pathSegments = checkNotNull(pathSegments);
+
+ //
+ // Precompute the AS Path Length:
+ // - AS_SET counts as 1
+ //
+ int pl = 0;
+ for (PathSegment pathSegment : pathSegments) {
+ if (pathSegment.getType() ==
+ BgpConstants.Update.AsPath.AS_SET) {
+ pl++;
+ continue;
+ }
+ pl += pathSegment.getSegmentAsNumbers().size();
+ }
+ asPathLength = pl;
+ }
+
+ /**
+ * Gets the AS Path Segments.
+ *
+ * @return the AS Path Segments
+ */
+ public ArrayList<PathSegment> getPathSegments() {
+ return pathSegments;
+ }
+
+ /**
+ * Gets the AS Path Length as considered by the BGP Decision Process.
+ *
+ * @return the AS Path Length as considered by the BGP Decision Process
+ */
+ int getAsPathLength() {
+ return asPathLength;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof AsPath)) {
+ return false;
+ }
+
+ AsPath otherAsPath = (AsPath) other;
+ return Objects.equals(this.pathSegments, otherAsPath.pathSegments);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(pathSegments);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("pathSegments", this.pathSegments)
+ .toString();
+ }
+ }
+
+ /**
+ * Compares whether two objects are equal.
+ * <p/>
+ * NOTE: The bgpSession field is excluded from the comparison.
+ *
+ * @return true if the two objects are equal, otherwise false.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ //
+ // NOTE: Subclasses are considered as change of identity, hence
+ // equals() will return false if the class type doesn't match.
+ //
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ if (!super.equals(other)) {
+ return false;
+ }
+
+ // NOTE: The bgpSession field is excluded from the comparison
+ BgpRouteEntry otherRoute = (BgpRouteEntry) other;
+ return (this.origin == otherRoute.origin) &&
+ Objects.equals(this.asPath, otherRoute.asPath) &&
+ (this.localPref == otherRoute.localPref) &&
+ (this.multiExitDisc == otherRoute.multiExitDisc);
+ }
+
+ /**
+ * Computes the hash code.
+ * <p/>
+ * NOTE: We return the base class hash code to avoid expensive computation
+ *
+ * @return the object hash code
+ */
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("prefix", prefix())
+ .add("nextHop", nextHop())
+ .add("bgpId", bgpSession.getRemoteBgpId())
+ .add("origin", origin)
+ .add("asPath", asPath)
+ .add("localPref", localPref)
+ .add("multiExitDisc", multiExitDisc)
+ .toString();
+ }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
new file mode 100644
index 0000000..0b39bb7
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
@@ -0,0 +1,1840 @@
+package org.onlab.onos.sdnip.bgp;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.SimpleChannelHandler;
+import org.jboss.netty.util.HashedWheelTimer;
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.Timer;
+import org.jboss.netty.util.TimerTask;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.HoldTimerExpired;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.OpenMessageError;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.UpdateMessageError;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class for handling the BGP peer sessions.
+ * There is one instance per each BGP peer session.
+ */
+public class BgpSession extends SimpleChannelHandler {
+ private static final Logger log =
+ LoggerFactory.getLogger(BgpSession.class);
+
+ private final BgpSessionManager bgpSessionManager;
+
+ // Local flag to indicate the session is closed.
+ // It is used to avoid the Netty's asynchronous closing of a channel.
+ private boolean isClosed = false;
+
+ private SocketAddress remoteAddress; // Peer IP addr/port
+ private IpAddress remoteIp4Address; // Peer IPv4 address
+ private int remoteBgpVersion; // 1 octet
+ private long remoteAs; // 2 octets
+ private long remoteHoldtime; // 2 octets
+ private IpAddress remoteBgpId; // 4 octets -> IPv4 address
+ //
+ private SocketAddress localAddress; // Local IP addr/port
+ private IpAddress localIp4Address; // Local IPv4 address
+ private int localBgpVersion; // 1 octet
+ private long localAs; // 2 octets
+ private long localHoldtime; // 2 octets
+ private IpAddress localBgpId; // 4 octets -> IPv4 address
+ //
+ private long localKeepaliveInterval; // Keepalive interval
+
+ // Timers state
+ private Timer timer = new HashedWheelTimer();
+ private volatile Timeout keepaliveTimeout; // Periodic KEEPALIVE
+ private volatile Timeout sessionTimeout; // Session timeout
+
+ // BGP RIB-IN routing entries from this peer
+ private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRibIn =
+ new ConcurrentHashMap<>();
+
+ /**
+ * Constructor for a given BGP Session Manager.
+ *
+ * @param bgpSessionManager the BGP Session Manager to use
+ */
+ BgpSession(BgpSessionManager bgpSessionManager) {
+ this.bgpSessionManager = bgpSessionManager;
+ }
+
+ /**
+ * Gets the BGP RIB-IN routing entries.
+ *
+ * @return the BGP RIB-IN routing entries
+ */
+ public Collection<BgpRouteEntry> getBgpRibIn() {
+ return bgpRibIn.values();
+ }
+
+ /**
+ * Finds a BGP routing entry in the BGP RIB-IN.
+ *
+ * @param prefix the prefix of the route to search for
+ * @return the BGP routing entry if found, otherwise null
+ */
+ public BgpRouteEntry findBgpRouteEntry(IpPrefix prefix) {
+ return bgpRibIn.get(prefix);
+ }
+
+ /**
+ * Gets the BGP session remote address.
+ *
+ * @return the BGP session remote address
+ */
+ public SocketAddress getRemoteAddress() {
+ return remoteAddress;
+ }
+
+ /**
+ * Gets the BGP session remote IPv4 address.
+ *
+ * @return the BGP session remote IPv4 address
+ */
+ public IpAddress getRemoteIp4Address() {
+ return remoteIp4Address;
+ }
+
+ /**
+ * Gets the BGP session remote BGP version.
+ *
+ * @return the BGP session remote BGP version
+ */
+ public int getRemoteBgpVersion() {
+ return remoteBgpVersion;
+ }
+
+ /**
+ * Gets the BGP session remote AS number.
+ *
+ * @return the BGP session remote AS number
+ */
+ public long getRemoteAs() {
+ return remoteAs;
+ }
+
+ /**
+ * Gets the BGP session remote Holdtime.
+ *
+ * @return the BGP session remote Holdtime
+ */
+ public long getRemoteHoldtime() {
+ return remoteHoldtime;
+ }
+
+ /**
+ * Gets the BGP session remote BGP Identifier as an IPv4 address.
+ *
+ * @return the BGP session remote BGP Identifier as an IPv4 address
+ */
+ public IpAddress getRemoteBgpId() {
+ return remoteBgpId;
+ }
+
+ /**
+ * Gets the BGP session local address.
+ *
+ * @return the BGP session local address
+ */
+ public SocketAddress getLocalAddress() {
+ return localAddress;
+ }
+
+ /**
+ * Gets the BGP session local BGP version.
+ *
+ * @return the BGP session local BGP version
+ */
+ public int getLocalBgpVersion() {
+ return localBgpVersion;
+ }
+
+ /**
+ * Gets the BGP session local AS number.
+ *
+ * @return the BGP session local AS number
+ */
+ public long getLocalAs() {
+ return localAs;
+ }
+
+ /**
+ * Gets the BGP session local Holdtime.
+ *
+ * @return the BGP session local Holdtime
+ */
+ public long getLocalHoldtime() {
+ return localHoldtime;
+ }
+
+ /**
+ * Gets the BGP session local BGP Identifier as an IPv4 address.
+ *
+ * @return the BGP session local BGP Identifier as an IPv4 address
+ */
+ public IpAddress getLocalBgpId() {
+ return localBgpId;
+ }
+
+ /**
+ * Tests whether the session is closed.
+ * <p/>
+ * NOTE: We use this method to avoid the Netty's asynchronous closing
+ * of a channel.
+ *
+ * @param return true if the session is closed
+ */
+ boolean isClosed() {
+ return isClosed;
+ }
+
+ /**
+ * Closes the channel.
+ *
+ * @param ctx the Channel Handler Context
+ */
+ void closeChannel(ChannelHandlerContext ctx) {
+ isClosed = true;
+ timer.stop();
+ ctx.getChannel().close();
+ }
+
+ @Override
+ public void channelConnected(ChannelHandlerContext ctx,
+ ChannelStateEvent channelEvent) {
+ localAddress = ctx.getChannel().getLocalAddress();
+ remoteAddress = ctx.getChannel().getRemoteAddress();
+
+ // Assign the local and remote IPv4 addresses
+ InetAddress inetAddr;
+ if (localAddress instanceof InetSocketAddress) {
+ inetAddr = ((InetSocketAddress) localAddress).getAddress();
+ localIp4Address = IpAddress.valueOf(inetAddr.getAddress());
+ }
+ if (remoteAddress instanceof InetSocketAddress) {
+ inetAddr = ((InetSocketAddress) remoteAddress).getAddress();
+ remoteIp4Address = IpAddress.valueOf(inetAddr.getAddress());
+ }
+
+ log.debug("BGP Session Connected from {} on {}",
+ remoteAddress, localAddress);
+ if (!bgpSessionManager.peerConnected(this)) {
+ log.debug("Cannot setup BGP Session Connection from {}. Closing...",
+ remoteAddress);
+ ctx.getChannel().close();
+ }
+ }
+
+ @Override
+ public void channelDisconnected(ChannelHandlerContext ctx,
+ ChannelStateEvent channelEvent) {
+ log.debug("BGP Session Disconnected from {} on {}",
+ ctx.getChannel().getRemoteAddress(),
+ ctx.getChannel().getLocalAddress());
+
+ //
+ // Withdraw the routes advertised by this BGP peer
+ //
+ // NOTE: We must initialize the RIB-IN before propagating the withdraws
+ // for further processing. Otherwise, the BGP Decision Process
+ // will use those routes again.
+ //
+ Collection<BgpRouteEntry> deletedRoutes = bgpRibIn.values();
+ bgpRibIn = new ConcurrentHashMap<>();
+
+ // Push the updates to the BGP Merged RIB
+ BgpSessionManager.BgpRouteSelector bgpRouteSelector =
+ bgpSessionManager.getBgpRouteSelector();
+ Collection<BgpRouteEntry> addedRoutes = Collections.emptyList();
+ bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes);
+
+ bgpSessionManager.peerDisconnected(this);
+ }
+
+ /**
+ * Processes BGP OPEN message.
+ *
+ * @param ctx the Channel Handler Context
+ * @param message the message to process
+ */
+ void processBgpOpen(ChannelHandlerContext ctx, ChannelBuffer message) {
+ int minLength =
+ BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+ if (message.readableBytes() < minLength) {
+ log.debug("BGP RX OPEN Error from {}: " +
+ "Message length {} too short. Must be at least {}",
+ remoteAddress, message.readableBytes(), minLength);
+ //
+ // ERROR: Bad Message Length
+ //
+ // Send NOTIFICATION and close the connection
+ ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
+ message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ return;
+ }
+
+ //
+ // Parse the OPEN message
+ //
+ // Remote BGP version
+ remoteBgpVersion = message.readUnsignedByte();
+ if (remoteBgpVersion != BgpConstants.BGP_VERSION) {
+ log.debug("BGP RX OPEN Error from {}: " +
+ "Unsupported BGP version {}. Should be {}",
+ remoteAddress, remoteBgpVersion,
+ BgpConstants.BGP_VERSION);
+ //
+ // ERROR: Unsupported Version Number
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = OpenMessageError.ERROR_CODE;
+ int errorSubcode = OpenMessageError.UNSUPPORTED_VERSION_NUMBER;
+ ChannelBuffer data = ChannelBuffers.buffer(2);
+ data.writeShort(BgpConstants.BGP_VERSION);
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, data);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ return;
+ }
+
+ // Remote AS number
+ remoteAs = message.readUnsignedShort();
+ //
+ // Verify that the AS number is same for all other BGP Sessions
+ // NOTE: This check applies only for our use-case where all BGP
+ // sessions are iBGP.
+ //
+ for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
+ if (remoteAs != bgpSession.getRemoteAs()) {
+ log.debug("BGP RX OPEN Error from {}: Bad Peer AS {}. " +
+ "Expected {}",
+ remoteAddress, remoteAs, bgpSession.getRemoteAs());
+ //
+ // ERROR: Bad Peer AS
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = OpenMessageError.ERROR_CODE;
+ int errorSubcode = OpenMessageError.BAD_PEER_AS;
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, null);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ return;
+ }
+ }
+
+ // Remote Hold Time
+ remoteHoldtime = message.readUnsignedShort();
+ if ((remoteHoldtime != 0) &&
+ (remoteHoldtime < BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME)) {
+ log.debug("BGP RX OPEN Error from {}: " +
+ "Unacceptable Hold Time field {}. " +
+ "Should be 0 or at least {}",
+ remoteAddress, remoteHoldtime,
+ BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME);
+ //
+ // ERROR: Unacceptable Hold Time
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = OpenMessageError.ERROR_CODE;
+ int errorSubcode = OpenMessageError.UNACCEPTABLE_HOLD_TIME;
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, null);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ return;
+ }
+
+ // Remote BGP Identifier
+ remoteBgpId = IpAddress.valueOf((int) message.readUnsignedInt());
+
+ // Optional Parameters
+ int optParamLen = message.readUnsignedByte();
+ if (message.readableBytes() < optParamLen) {
+ log.debug("BGP RX OPEN Error from {}: " +
+ "Invalid Optional Parameter Length field {}. " +
+ "Remaining Optional Parameters {}",
+ remoteAddress, optParamLen, message.readableBytes());
+ //
+ // ERROR: Invalid Optional Parameter Length field: Unspecific
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = OpenMessageError.ERROR_CODE;
+ int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC;
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, null);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ return;
+ }
+ // TODO: Parse the optional parameters (if needed)
+ message.readBytes(optParamLen); // NOTE: data ignored
+
+ //
+ // Copy some of the remote peer's state/setup to the local setup:
+ // - BGP version
+ // - AS number (NOTE: the peer setup is always iBGP)
+ // - Holdtime
+ // Also, assign the local BGP ID based on the local setup
+ //
+ localBgpVersion = remoteBgpVersion;
+ localAs = remoteAs;
+ localHoldtime = remoteHoldtime;
+ localBgpId = bgpSessionManager.getMyBgpId();
+
+ // Set the Keepalive interval
+ if (localHoldtime == 0) {
+ localKeepaliveInterval = 0;
+ } else {
+ localKeepaliveInterval = Math.max(localHoldtime /
+ BgpConstants.BGP_KEEPALIVE_PER_HOLD_INTERVAL,
+ BgpConstants.BGP_KEEPALIVE_MIN_INTERVAL);
+ }
+
+ log.debug("BGP RX OPEN message from {}: " +
+ "BGPv{} AS {} BGP-ID {} Holdtime {}",
+ remoteAddress, remoteBgpVersion, remoteAs,
+ remoteBgpId, remoteHoldtime);
+
+ // Send my OPEN followed by KEEPALIVE
+ ChannelBuffer txMessage = prepareBgpOpen();
+ ctx.getChannel().write(txMessage);
+ //
+ txMessage = prepareBgpKeepalive();
+ ctx.getChannel().write(txMessage);
+
+ // Start the KEEPALIVE timer
+ restartKeepaliveTimer(ctx);
+
+ // Start the Session Timeout timer
+ restartSessionTimeoutTimer(ctx);
+ }
+
+ /**
+ * Processes BGP UPDATE message.
+ *
+ * @param ctx the Channel Handler Context
+ * @param message the message to process
+ */
+ void processBgpUpdate(ChannelHandlerContext ctx, ChannelBuffer message) {
+ Collection<BgpRouteEntry> addedRoutes = null;
+ Map<IpPrefix, BgpRouteEntry> deletedRoutes = new HashMap<>();
+
+ int minLength =
+ BgpConstants.BGP_UPDATE_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+ if (message.readableBytes() < minLength) {
+ log.debug("BGP RX UPDATE Error from {}: " +
+ "Message length {} too short. Must be at least {}",
+ remoteAddress, message.readableBytes(), minLength);
+ //
+ // ERROR: Bad Message Length
+ //
+ // Send NOTIFICATION and close the connection
+ ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
+ message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ return;
+ }
+
+ log.debug("BGP RX UPDATE message from {}", remoteAddress);
+
+ //
+ // Parse the UPDATE message
+ //
+
+ //
+ // Parse the Withdrawn Routes
+ //
+ int withdrawnRoutesLength = message.readUnsignedShort();
+ if (withdrawnRoutesLength > message.readableBytes()) {
+ // ERROR: Malformed Attribute List
+ actionsBgpUpdateMalformedAttributeList(ctx);
+ return;
+ }
+ Collection<IpPrefix> withdrawnPrefixes = null;
+ try {
+ withdrawnPrefixes = parsePackedPrefixes(withdrawnRoutesLength,
+ message);
+ } catch (BgpParseException e) {
+ // ERROR: Invalid Network Field
+ log.debug("Exception parsing Withdrawn Prefixes from BGP peer {}: ",
+ remoteBgpId, e);
+ actionsBgpUpdateInvalidNetworkField(ctx);
+ return;
+ }
+ for (IpPrefix prefix : withdrawnPrefixes) {
+ log.debug("BGP RX UPDATE message WITHDRAWN from {}: {}",
+ remoteAddress, prefix);
+ BgpRouteEntry bgpRouteEntry = bgpRibIn.get(prefix);
+ if (bgpRouteEntry != null) {
+ deletedRoutes.put(prefix, bgpRouteEntry);
+ }
+ }
+
+ //
+ // Parse the Path Attributes
+ //
+ try {
+ addedRoutes = parsePathAttributes(ctx, message);
+ } catch (BgpParseException e) {
+ log.debug("Exception parsing Path Attributes from BGP peer {}: ",
+ remoteBgpId, e);
+ // NOTE: The session was already closed, so nothing else to do
+ return;
+ }
+ // Ignore WITHDRAWN routes that are ADDED
+ for (BgpRouteEntry bgpRouteEntry : addedRoutes) {
+ deletedRoutes.remove(bgpRouteEntry.prefix());
+ }
+
+ // Update the BGP RIB-IN
+ for (BgpRouteEntry bgpRouteEntry : deletedRoutes.values()) {
+ bgpRibIn.remove(bgpRouteEntry.prefix());
+ }
+ for (BgpRouteEntry bgpRouteEntry : addedRoutes) {
+ bgpRibIn.put(bgpRouteEntry.prefix(), bgpRouteEntry);
+ }
+
+ // Push the updates to the BGP Merged RIB
+ BgpSessionManager.BgpRouteSelector bgpRouteSelector =
+ bgpSessionManager.getBgpRouteSelector();
+ bgpRouteSelector.routeUpdates(this, addedRoutes,
+ deletedRoutes.values());
+
+ // Start the Session Timeout timer
+ restartSessionTimeoutTimer(ctx);
+ }
+
+ /**
+ * Parse BGP Path Attributes from the BGP UPDATE message.
+ *
+ * @param ctx the Channel Handler Context
+ * @param message the message to parse
+ * @return a collection of the result BGP Route Entries
+ * @throws BgpParseException
+ */
+ private Collection<BgpRouteEntry> parsePathAttributes(
+ ChannelHandlerContext ctx,
+ ChannelBuffer message)
+ throws BgpParseException {
+ Map<IpPrefix, BgpRouteEntry> addedRoutes = new HashMap<>();
+
+ //
+ // Parsed values
+ //
+ Short origin = -1; // Mandatory
+ BgpRouteEntry.AsPath asPath = null; // Mandatory
+ IpAddress nextHop = null; // Mandatory
+ long multiExitDisc = // Optional
+ BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
+ Long localPref = null; // Mandatory
+ Long aggregatorAsNumber = null; // Optional: unused
+ IpAddress aggregatorIpAddress = null; // Optional: unused
+
+ //
+ // Get and verify the Path Attributes Length
+ //
+ int pathAttributeLength = message.readUnsignedShort();
+ if (pathAttributeLength > message.readableBytes()) {
+ // ERROR: Malformed Attribute List
+ actionsBgpUpdateMalformedAttributeList(ctx);
+ String errorMsg = "Malformed Attribute List";
+ throw new BgpParseException(errorMsg);
+ }
+ if (pathAttributeLength == 0) {
+ return addedRoutes.values();
+ }
+
+ //
+ // Parse the Path Attributes
+ //
+ int pathAttributeEnd = message.readerIndex() + pathAttributeLength;
+ while (message.readerIndex() < pathAttributeEnd) {
+ int attrFlags = message.readUnsignedByte();
+ if (message.readerIndex() >= pathAttributeEnd) {
+ // ERROR: Malformed Attribute List
+ actionsBgpUpdateMalformedAttributeList(ctx);
+ String errorMsg = "Malformed Attribute List";
+ throw new BgpParseException(errorMsg);
+ }
+ int attrTypeCode = message.readUnsignedByte();
+
+ // The Attribute Flags
+ boolean optionalBit = ((0x80 & attrFlags) != 0);
+ boolean transitiveBit = ((0x40 & attrFlags) != 0);
+ boolean partialBit = ((0x20 & attrFlags) != 0);
+ boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
+
+ // The Attribute Length
+ int attrLen = 0;
+ int attrLenOctets = 1;
+ if (extendedLengthBit) {
+ attrLenOctets = 2;
+ }
+ if (message.readerIndex() + attrLenOctets > pathAttributeEnd) {
+ // ERROR: Malformed Attribute List
+ actionsBgpUpdateMalformedAttributeList(ctx);
+ String errorMsg = "Malformed Attribute List";
+ throw new BgpParseException(errorMsg);
+ }
+ if (extendedLengthBit) {
+ attrLen = message.readUnsignedShort();
+ } else {
+ attrLen = message.readUnsignedByte();
+ }
+ if (message.readerIndex() + attrLen > pathAttributeEnd) {
+ // ERROR: Malformed Attribute List
+ actionsBgpUpdateMalformedAttributeList(ctx);
+ String errorMsg = "Malformed Attribute List";
+ throw new BgpParseException(errorMsg);
+ }
+
+ //
+ // Verify the Attribute Flags
+ //
+ verifyBgpUpdateAttributeFlags(ctx, attrTypeCode, attrLen,
+ attrFlags, message);
+
+ //
+ // Extract the Attribute Value based on the Attribute Type Code
+ //
+ switch (attrTypeCode) {
+
+ case BgpConstants.Update.Origin.TYPE:
+ // Attribute Type Code ORIGIN
+ origin = parseAttributeTypeOrigin(ctx, attrTypeCode, attrLen,
+ attrFlags, message);
+ break;
+
+ case BgpConstants.Update.AsPath.TYPE:
+ // Attribute Type Code AS_PATH
+ asPath = parseAttributeTypeAsPath(ctx, attrTypeCode, attrLen,
+ attrFlags, message);
+ break;
+
+ case BgpConstants.Update.NextHop.TYPE:
+ // Attribute Type Code NEXT_HOP
+ nextHop = parseAttributeTypeNextHop(ctx, attrTypeCode, attrLen,
+ attrFlags, message);
+ break;
+
+ case BgpConstants.Update.MultiExitDisc.TYPE:
+ // Attribute Type Code MULTI_EXIT_DISC
+ multiExitDisc =
+ parseAttributeTypeMultiExitDisc(ctx, attrTypeCode, attrLen,
+ attrFlags, message);
+ break;
+
+ case BgpConstants.Update.LocalPref.TYPE:
+ // Attribute Type Code LOCAL_PREF
+ localPref =
+ parseAttributeTypeLocalPref(ctx, attrTypeCode, attrLen,
+ attrFlags, message);
+ break;
+
+ case BgpConstants.Update.AtomicAggregate.TYPE:
+ // Attribute Type Code ATOMIC_AGGREGATE
+ parseAttributeTypeAtomicAggregate(ctx, attrTypeCode, attrLen,
+ attrFlags, message);
+ // Nothing to do: this attribute is primarily informational
+ break;
+
+ case BgpConstants.Update.Aggregator.TYPE:
+ // Attribute Type Code AGGREGATOR
+ Pair<Long, IpAddress> aggregator =
+ parseAttributeTypeAggregator(ctx, attrTypeCode, attrLen,
+ attrFlags, message);
+ aggregatorAsNumber = aggregator.getLeft();
+ aggregatorIpAddress = aggregator.getRight();
+ break;
+
+ default:
+ // TODO: Parse any new Attribute Types if needed
+ if (!optionalBit) {
+ // ERROR: Unrecognized Well-known Attribute
+ actionsBgpUpdateUnrecognizedWellKnownAttribute(
+ ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Unrecognized Well-known Attribute: " +
+ attrTypeCode;
+ throw new BgpParseException(errorMsg);
+ }
+
+ // Skip the data from the unrecognized attribute
+ log.debug("BGP RX UPDATE message from {}: " +
+ "Unrecognized Attribute Type {}",
+ remoteAddress, attrTypeCode);
+ message.skipBytes(attrLen);
+ break;
+ }
+ }
+
+ //
+ // Verify the Well-known Attributes
+ //
+ verifyBgpUpdateWellKnownAttributes(ctx, origin, asPath, nextHop,
+ localPref);
+
+ //
+ // Parse the NLRI (Network Layer Reachability Information)
+ //
+ Collection<IpPrefix> addedPrefixes = null;
+ int nlriLength = message.readableBytes();
+ try {
+ addedPrefixes = parsePackedPrefixes(nlriLength, message);
+ } catch (BgpParseException e) {
+ // ERROR: Invalid Network Field
+ log.debug("Exception parsing NLRI from BGP peer {}: ",
+ remoteBgpId, e);
+ actionsBgpUpdateInvalidNetworkField(ctx);
+ // Rethrow the exception
+ throw e;
+ }
+
+ // Generate the added routes
+ for (IpPrefix prefix : addedPrefixes) {
+ BgpRouteEntry bgpRouteEntry =
+ new BgpRouteEntry(this, prefix, nextHop,
+ origin.byteValue(), asPath, localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+ if (bgpRouteEntry.hasAsPathLoop(localAs)) {
+ log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
+ "nextHop {}: contains AS Path loop",
+ remoteAddress, prefix, nextHop);
+ continue;
+ } else {
+ log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
+ remoteAddress, prefix, nextHop);
+ }
+ addedRoutes.put(prefix, bgpRouteEntry);
+ }
+
+ return addedRoutes.values();
+ }
+
+ /**
+ * Verifies BGP UPDATE Well-known Attributes.
+ *
+ * @param ctx the Channel Handler Context
+ * @param origin the ORIGIN well-known mandatory attribute
+ * @param asPath the AS_PATH well-known mandatory attribute
+ * @param nextHop the NEXT_HOP well-known mandatory attribute
+ * @param localPref the LOCAL_PREF required attribute
+ * @throws BgpParseException
+ */
+ private void verifyBgpUpdateWellKnownAttributes(
+ ChannelHandlerContext ctx,
+ Short origin,
+ BgpRouteEntry.AsPath asPath,
+ IpAddress nextHop,
+ Long localPref)
+ throws BgpParseException {
+ //
+ // Check for Missing Well-known Attributes
+ //
+ if ((origin == null) || (origin == -1)) {
+ // Missing Attribute Type Code ORIGIN
+ int type = BgpConstants.Update.Origin.TYPE;
+ actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
+ String errorMsg = "Missing Well-known Attribute: ORIGIN";
+ throw new BgpParseException(errorMsg);
+ }
+ if (asPath == null) {
+ // Missing Attribute Type Code AS_PATH
+ int type = BgpConstants.Update.AsPath.TYPE;
+ actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
+ String errorMsg = "Missing Well-known Attribute: AS_PATH";
+ throw new BgpParseException(errorMsg);
+ }
+ if (nextHop == null) {
+ // Missing Attribute Type Code NEXT_HOP
+ int type = BgpConstants.Update.NextHop.TYPE;
+ actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
+ String errorMsg = "Missing Well-known Attribute: NEXT_HOP";
+ throw new BgpParseException(errorMsg);
+ }
+ if (localPref == null) {
+ // Missing Attribute Type Code LOCAL_PREF
+ // NOTE: Required for iBGP
+ int type = BgpConstants.Update.LocalPref.TYPE;
+ actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
+ String errorMsg = "Missing Well-known Attribute: LOCAL_PREF";
+ throw new BgpParseException(errorMsg);
+ }
+ }
+
+ /**
+ * Verifies the BGP UPDATE Attribute Flags.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @throws BgpParseException
+ */
+ private void verifyBgpUpdateAttributeFlags(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpParseException {
+
+ //
+ // Assign the Attribute Type Name and the Well-known flag
+ //
+ String typeName = "UNKNOWN";
+ boolean isWellKnown = false;
+ switch (attrTypeCode) {
+ case BgpConstants.Update.Origin.TYPE:
+ isWellKnown = true;
+ typeName = "ORIGIN";
+ break;
+ case BgpConstants.Update.AsPath.TYPE:
+ isWellKnown = true;
+ typeName = "AS_PATH";
+ break;
+ case BgpConstants.Update.NextHop.TYPE:
+ isWellKnown = true;
+ typeName = "NEXT_HOP";
+ break;
+ case BgpConstants.Update.MultiExitDisc.TYPE:
+ isWellKnown = false;
+ typeName = "MULTI_EXIT_DISC";
+ break;
+ case BgpConstants.Update.LocalPref.TYPE:
+ isWellKnown = true;
+ typeName = "LOCAL_PREF";
+ break;
+ case BgpConstants.Update.AtomicAggregate.TYPE:
+ isWellKnown = true;
+ typeName = "ATOMIC_AGGREGATE";
+ break;
+ case BgpConstants.Update.Aggregator.TYPE:
+ isWellKnown = false;
+ typeName = "AGGREGATOR";
+ break;
+ default:
+ isWellKnown = false;
+ typeName = "UNKNOWN(" + attrTypeCode + ")";
+ break;
+ }
+
+ //
+ // Verify the Attribute Flags
+ //
+ boolean optionalBit = ((0x80 & attrFlags) != 0);
+ boolean transitiveBit = ((0x40 & attrFlags) != 0);
+ boolean partialBit = ((0x20 & attrFlags) != 0);
+ if ((isWellKnown && optionalBit) ||
+ (isWellKnown && (!transitiveBit)) ||
+ (isWellKnown && partialBit) ||
+ (optionalBit && (!transitiveBit) && partialBit)) {
+ //
+ // ERROR: The Optional bit cannot be set for Well-known attributes
+ // ERROR: The Transtive bit MUST be 1 for well-known attributes
+ // ERROR: The Partial bit MUST be 0 for well-known attributes
+ // ERROR: The Partial bit MUST be 0 for optional non-transitive
+ // attributes
+ //
+ actionsBgpUpdateAttributeFlagsError(
+ ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Flags Error for " + typeName + ": " +
+ attrFlags;
+ throw new BgpParseException(errorMsg);
+ }
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type ORIGIN.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed ORIGIN value
+ * @throws BgpParseException
+ */
+ private short parseAttributeTypeOrigin(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.Origin.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpParseException(errorMsg);
+ }
+
+ message.markReaderIndex();
+ short origin = message.readUnsignedByte();
+ switch (origin) {
+ case BgpConstants.Update.Origin.IGP:
+ // FALLTHROUGH
+ case BgpConstants.Update.Origin.EGP:
+ // FALLTHROUGH
+ case BgpConstants.Update.Origin.INCOMPLETE:
+ break;
+ default:
+ // ERROR: Invalid ORIGIN Attribute
+ message.resetReaderIndex();
+ actionsBgpUpdateInvalidOriginAttribute(
+ ctx, attrTypeCode, attrLen, attrFlags, message, origin);
+ String errorMsg = "Invalid ORIGIN Attribute: " + origin;
+ throw new BgpParseException(errorMsg);
+ }
+
+ return origin;
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute AS Path.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed AS Path
+ * @throws BgpParseException
+ */
+ private BgpRouteEntry.AsPath parseAttributeTypeAsPath(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpParseException {
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+
+ //
+ // Parse the message
+ //
+ while (attrLen > 0) {
+ if (attrLen < 2) {
+ // ERROR: Malformed AS_PATH
+ actionsBgpUpdateMalformedAsPath(ctx);
+ String errorMsg = "Malformed AS Path";
+ throw new BgpParseException(errorMsg);
+ }
+ // Get the Path Segment Type and Length (in number of ASes)
+ short pathSegmentType = message.readUnsignedByte();
+ short pathSegmentLength = message.readUnsignedByte();
+ attrLen -= 2;
+
+ // Verify the Path Segment Type
+ switch (pathSegmentType) {
+ case BgpConstants.Update.AsPath.AS_SET:
+ // FALLTHROUGH
+ case BgpConstants.Update.AsPath.AS_SEQUENCE:
+ break;
+ default:
+ // ERROR: Invalid Path Segment Type
+ //
+ // NOTE: The BGP Spec (RFC 4271) doesn't contain Error Subcode
+ // for "Invalid Path Segment Type", hence we return
+ // the error as "Malformed AS_PATH".
+ //
+ actionsBgpUpdateMalformedAsPath(ctx);
+ String errorMsg =
+ "Invalid AS Path Segment Type: " + pathSegmentType;
+ throw new BgpParseException(errorMsg);
+ }
+
+ // Parse the AS numbers
+ if (2 * pathSegmentLength > attrLen) {
+ // ERROR: Malformed AS_PATH
+ actionsBgpUpdateMalformedAsPath(ctx);
+ String errorMsg = "Malformed AS Path";
+ throw new BgpParseException(errorMsg);
+ }
+ attrLen -= (2 * pathSegmentLength);
+ ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+ while (pathSegmentLength-- > 0) {
+ long asNumber = message.readUnsignedShort();
+ segmentAsNumbers.add(asNumber);
+ }
+
+ BgpRouteEntry.PathSegment pathSegment =
+ new BgpRouteEntry.PathSegment((byte) pathSegmentType,
+ segmentAsNumbers);
+ pathSegments.add(pathSegment);
+ }
+
+ return new BgpRouteEntry.AsPath(pathSegments);
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type NEXT_HOP.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed NEXT_HOP value
+ * @throws BgpParseException
+ */
+ private IpAddress parseAttributeTypeNextHop(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.NextHop.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpParseException(errorMsg);
+ }
+
+ message.markReaderIndex();
+ long address = message.readUnsignedInt();
+ IpAddress nextHopAddress = IpAddress.valueOf((int) address);
+ //
+ // Check whether the NEXT_HOP IP address is semantically correct.
+ // As per RFC 4271, Section 6.3:
+ //
+ // a) It MUST NOT be the IP address of the receiving speaker
+ // b) In the case of an EBGP ....
+ //
+ // Here we check only (a), because (b) doesn't apply for us: all our
+ // peers are iBGP.
+ //
+ if (nextHopAddress.equals(localIp4Address)) {
+ // ERROR: Invalid NEXT_HOP Attribute
+ message.resetReaderIndex();
+ actionsBgpUpdateInvalidNextHopAttribute(
+ ctx, attrTypeCode, attrLen, attrFlags, message,
+ nextHopAddress);
+ String errorMsg = "Invalid NEXT_HOP Attribute: " + nextHopAddress;
+ throw new BgpParseException(errorMsg);
+ }
+
+ return nextHopAddress;
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type MULTI_EXIT_DISC.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed MULTI_EXIT_DISC value
+ * @throws BgpParseException
+ */
+ private long parseAttributeTypeMultiExitDisc(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.MultiExitDisc.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpParseException(errorMsg);
+ }
+
+ long multiExitDisc = message.readUnsignedInt();
+ return multiExitDisc;
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type LOCAL_PREF.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed LOCAL_PREF value
+ * @throws BgpParseException
+ */
+ private long parseAttributeTypeLocalPref(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.LocalPref.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpParseException(errorMsg);
+ }
+
+ long localPref = message.readUnsignedInt();
+ return localPref;
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type ATOMIC_AGGREGATE.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @throws BgpParseException
+ */
+ private void parseAttributeTypeAtomicAggregate(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.AtomicAggregate.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpParseException(errorMsg);
+ }
+
+ // Nothing to do: this attribute is primarily informational
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type AGGREGATOR.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed AGGREGATOR value: a tuple of <AS-Number, IP-Address>
+ * @throws BgpParseException
+ */
+ private Pair<Long, IpAddress> parseAttributeTypeAggregator(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.Aggregator.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpParseException(errorMsg);
+ }
+
+ // The AGGREGATOR AS number
+ long aggregatorAsNumber = message.readUnsignedShort();
+ // The AGGREGATOR IP address
+ long aggregatorAddress = message.readUnsignedInt();
+ IpAddress aggregatorIpAddress =
+ IpAddress.valueOf((int) aggregatorAddress);
+
+ Pair<Long, IpAddress> aggregator = Pair.of(aggregatorAsNumber,
+ aggregatorIpAddress);
+ return aggregator;
+ }
+
+ /**
+ * Parses a message that contains encoded IPv4 network prefixes.
+ * <p>
+ * The IPv4 prefixes are encoded in the form:
+ * <Length, Prefix> where Length is the length in bits of the IPv4 prefix,
+ * and Prefix is the IPv4 prefix (padded with trailing bits to the end
+ * of an octet).
+ *
+ * @param totalLength the total length of the data to parse
+ * @param message the message with data to parse
+ * @return a collection of parsed IPv4 network prefixes
+ * @throws BgpParseException
+ */
+ private Collection<IpPrefix> parsePackedPrefixes(int totalLength,
+ ChannelBuffer message)
+ throws BgpParseException {
+ Collection<IpPrefix> result = new ArrayList<>();
+
+ if (totalLength == 0) {
+ return result;
+ }
+
+ // Parse the data
+ int dataEnd = message.readerIndex() + totalLength;
+ while (message.readerIndex() < dataEnd) {
+ int prefixBitlen = message.readUnsignedByte();
+ int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up
+ if (message.readerIndex() + prefixBytelen > dataEnd) {
+ String errorMsg = "Malformed Network Prefixes";
+ throw new BgpParseException(errorMsg);
+ }
+
+ long address = 0;
+ long extraShift = (4 - prefixBytelen) * 8;
+ while (prefixBytelen > 0) {
+ address <<= 8;
+ address |= message.readUnsignedByte();
+ prefixBytelen--;
+ }
+ address <<= extraShift;
+ IpPrefix prefix =
+ IpPrefix.valueOf(IpAddress.valueOf((int) address).toInt(),
+ (short) prefixBitlen);
+ result.add(prefix);
+ }
+
+ return result;
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Invalid Network Field Error: send NOTIFICATION and close the channel.
+ *
+ * @param ctx the Channel Handler Context
+ */
+ private void actionsBgpUpdateInvalidNetworkField(
+ ChannelHandlerContext ctx) {
+ log.debug("BGP RX UPDATE Error from {}: Invalid Network Field",
+ remoteAddress);
+
+ //
+ // ERROR: Invalid Network Field
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = UpdateMessageError.ERROR_CODE;
+ int errorSubcode = UpdateMessageError.INVALID_NETWORK_FIELD;
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, null);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Malformed Attribute List Error: send NOTIFICATION and close the channel.
+ *
+ * @param ctx the Channel Handler Context
+ */
+ private void actionsBgpUpdateMalformedAttributeList(
+ ChannelHandlerContext ctx) {
+ log.debug("BGP RX UPDATE Error from {}: Malformed Attribute List",
+ remoteAddress);
+
+ //
+ // ERROR: Malformed Attribute List
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = UpdateMessageError.ERROR_CODE;
+ int errorSubcode = UpdateMessageError.MALFORMED_ATTRIBUTE_LIST;
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, null);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Missing Well-known Attribute Error: send NOTIFICATION and close the
+ * channel.
+ *
+ * @param ctx the Channel Handler Context
+ * @param missingAttrTypeCode the missing attribute type code
+ */
+ private void actionsBgpUpdateMissingWellKnownAttribute(
+ ChannelHandlerContext ctx,
+ int missingAttrTypeCode) {
+ log.debug("BGP RX UPDATE Error from {}: Missing Well-known Attribute: {}",
+ remoteAddress, missingAttrTypeCode);
+
+ //
+ // ERROR: Missing Well-known Attribute
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = UpdateMessageError.ERROR_CODE;
+ int errorSubcode = UpdateMessageError.MISSING_WELL_KNOWN_ATTRIBUTE;
+ ChannelBuffer data = ChannelBuffers.buffer(1);
+ data.writeByte(missingAttrTypeCode);
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, data);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Invalid ORIGIN Attribute Error: send NOTIFICATION and close the channel.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ * @param origin the ORIGIN attribute value
+ */
+ private void actionsBgpUpdateInvalidOriginAttribute(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message,
+ short origin) {
+ log.debug("BGP RX UPDATE Error from {}: Invalid ORIGIN Attribute",
+ remoteAddress);
+
+ //
+ // ERROR: Invalid ORIGIN Attribute
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = UpdateMessageError.ERROR_CODE;
+ int errorSubcode = UpdateMessageError.INVALID_ORIGIN_ATTRIBUTE;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, data);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Attribute Flags Error: send NOTIFICATION and close the channel.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ */
+ private void actionsBgpUpdateAttributeFlagsError(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message) {
+ log.debug("BGP RX UPDATE Error from {}: Attribute Flags Error",
+ remoteAddress);
+
+ //
+ // ERROR: Attribute Flags Error
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = UpdateMessageError.ERROR_CODE;
+ int errorSubcode = UpdateMessageError.ATTRIBUTE_FLAGS_ERROR;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, data);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Invalid NEXT_HOP Attribute Error: send NOTIFICATION and close the
+ * channel.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ * @param nextHop the NEXT_HOP attribute value
+ */
+ private void actionsBgpUpdateInvalidNextHopAttribute(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message,
+ IpAddress nextHop) {
+ log.debug("BGP RX UPDATE Error from {}: Invalid NEXT_HOP Attribute {}",
+ remoteAddress, nextHop);
+
+ //
+ // ERROR: Invalid ORIGIN Attribute
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = UpdateMessageError.ERROR_CODE;
+ int errorSubcode = UpdateMessageError.INVALID_NEXT_HOP_ATTRIBUTE;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, data);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Unrecognized Well-known Attribute Error: send NOTIFICATION and close
+ * the channel.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ */
+ private void actionsBgpUpdateUnrecognizedWellKnownAttribute(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message) {
+ log.debug("BGP RX UPDATE Error from {}: " +
+ "Unrecognized Well-known Attribute Error: {}",
+ remoteAddress, attrTypeCode);
+
+ //
+ // ERROR: Unrecognized Well-known Attribute
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = UpdateMessageError.ERROR_CODE;
+ int errorSubcode =
+ UpdateMessageError.UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, data);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Attribute Length Error: send NOTIFICATION and close the channel.
+ *
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ */
+ private void actionsBgpUpdateAttributeLengthError(
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message) {
+ log.debug("BGP RX UPDATE Error from {}: Attribute Length Error",
+ remoteAddress);
+
+ //
+ // ERROR: Attribute Length Error
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = UpdateMessageError.ERROR_CODE;
+ int errorSubcode = UpdateMessageError.ATTRIBUTE_LENGTH_ERROR;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, data);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Malformed AS_PATH Error: send NOTIFICATION and close the channel.
+ *
+ * @param ctx the Channel Handler Context
+ */
+ private void actionsBgpUpdateMalformedAsPath(
+ ChannelHandlerContext ctx) {
+ log.debug("BGP RX UPDATE Error from {}: Malformed AS Path",
+ remoteAddress);
+
+ //
+ // ERROR: Malformed AS_PATH
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = UpdateMessageError.ERROR_CODE;
+ int errorSubcode = UpdateMessageError.MALFORMED_AS_PATH;
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, null);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+
+ /**
+ * Processes BGP NOTIFICATION message.
+ *
+ * @param ctx the Channel Handler Context
+ * @param message the message to process
+ */
+ void processBgpNotification(ChannelHandlerContext ctx,
+ ChannelBuffer message) {
+ int minLength =
+ BgpConstants.BGP_NOTIFICATION_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+ if (message.readableBytes() < minLength) {
+ log.debug("BGP RX NOTIFICATION Error from {}: " +
+ "Message length {} too short. Must be at least {}",
+ remoteAddress, message.readableBytes(), minLength);
+ //
+ // ERROR: Bad Message Length
+ //
+ // NOTE: We do NOT send NOTIFICATION in response to a notification
+ return;
+ }
+
+ //
+ // Parse the NOTIFICATION message
+ //
+ int errorCode = message.readUnsignedByte();
+ int errorSubcode = message.readUnsignedByte();
+ int dataLength = message.readableBytes();
+
+ log.debug("BGP RX NOTIFICATION message from {}: Error Code {} " +
+ "Error Subcode {} Data Length {}",
+ remoteAddress, errorCode, errorSubcode, dataLength);
+
+ //
+ // NOTE: If the peer sent a NOTIFICATION, we leave it to the peer to
+ // close the connection.
+ //
+
+ // Start the Session Timeout timer
+ restartSessionTimeoutTimer(ctx);
+ }
+
+ /**
+ * Processes BGP KEEPALIVE message.
+ *
+ * @param ctx the Channel Handler Context
+ * @param message the message to process
+ */
+ void processBgpKeepalive(ChannelHandlerContext ctx,
+ ChannelBuffer message) {
+ if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH !=
+ BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) {
+ log.debug("BGP RX KEEPALIVE Error from {}: " +
+ "Invalid total message length {}. Expected {}",
+ remoteAddress,
+ message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH,
+ BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH);
+ //
+ // ERROR: Bad Message Length
+ //
+ // Send NOTIFICATION and close the connection
+ ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
+ message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ return;
+ }
+
+ //
+ // Parse the KEEPALIVE message: nothing to do
+ //
+ log.debug("BGP RX KEEPALIVE message from {}", remoteAddress);
+
+ // Start the Session Timeout timer
+ restartSessionTimeoutTimer(ctx);
+ }
+
+ /**
+ * Prepares BGP OPEN message.
+ *
+ * @return the message to transmit (BGP header included)
+ */
+ private ChannelBuffer prepareBgpOpen() {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ //
+ // Prepare the OPEN message payload
+ //
+ message.writeByte(localBgpVersion);
+ message.writeShort((int) localAs);
+ message.writeShort((int) localHoldtime);
+ message.writeInt(bgpSessionManager.getMyBgpId().toInt());
+ message.writeByte(0); // No Optional Parameters
+ return prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN, message);
+ }
+
+ /**
+ * Prepares BGP KEEPALIVE message.
+ *
+ * @return the message to transmit (BGP header included)
+ */
+ private ChannelBuffer prepareBgpKeepalive() {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ //
+ // Prepare the KEEPALIVE message payload: nothing to do
+ //
+ return prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE, message);
+ }
+
+ /**
+ * Prepares BGP NOTIFICATION message.
+ *
+ * @param errorCode the BGP NOTIFICATION Error Code
+ * @param errorSubcode the BGP NOTIFICATION Error Subcode if applicable,
+ * otherwise BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC
+ * @param payload the BGP NOTIFICATION Data if applicable, otherwise null
+ * @return the message to transmit (BGP header included)
+ */
+ ChannelBuffer prepareBgpNotification(int errorCode, int errorSubcode,
+ ChannelBuffer data) {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ //
+ // Prepare the NOTIFICATION message payload
+ //
+ message.writeByte(errorCode);
+ message.writeByte(errorSubcode);
+ if (data != null) {
+ message.writeBytes(data);
+ }
+ return prepareBgpMessage(BgpConstants.BGP_TYPE_NOTIFICATION, message);
+ }
+
+ /**
+ * Prepares BGP NOTIFICATION message: Bad Message Length.
+ *
+ * @param length the erroneous Length field
+ * @return the message to transmit (BGP header included)
+ */
+ ChannelBuffer prepareBgpNotificationBadMessageLength(int length) {
+ int errorCode = MessageHeaderError.ERROR_CODE;
+ int errorSubcode = MessageHeaderError.BAD_MESSAGE_LENGTH;
+ ChannelBuffer data = ChannelBuffers.buffer(2);
+ data.writeShort(length);
+
+ return prepareBgpNotification(errorCode, errorSubcode, data);
+ }
+
+ /**
+ * Prepares BGP UPDATE Notification data payload.
+ *
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ * @return the buffer with the data payload for the BGP UPDATE Notification
+ */
+ private ChannelBuffer prepareBgpUpdateNotificationDataPayload(
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message) {
+ // Compute the attribute length field octets
+ boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
+ int attrLenOctets = 1;
+ if (extendedLengthBit) {
+ attrLenOctets = 2;
+ }
+ ChannelBuffer data =
+ ChannelBuffers.buffer(attrLen + attrLenOctets + 1);
+ data.writeByte(attrTypeCode);
+ if (extendedLengthBit) {
+ data.writeShort(attrLen);
+ } else {
+ data.writeByte(attrLen);
+ }
+ data.writeBytes(message, attrLen);
+ return data;
+ }
+
+ /**
+ * Prepares BGP message.
+ *
+ * @param type the BGP message type
+ * @param payload the message payload to transmit (BGP header excluded)
+ * @return the message to transmit (BGP header included)
+ */
+ private ChannelBuffer prepareBgpMessage(int type, ChannelBuffer payload) {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_HEADER_LENGTH +
+ payload.readableBytes());
+
+ // Write the marker
+ for (int i = 0; i < BgpConstants.BGP_HEADER_MARKER_LENGTH; i++) {
+ message.writeByte(0xff);
+ }
+
+ // Write the rest of the BGP header
+ message.writeShort(BgpConstants.BGP_HEADER_LENGTH +
+ payload.readableBytes());
+ message.writeByte(type);
+
+ // Write the payload
+ message.writeBytes(payload);
+ return message;
+ }
+
+ /**
+ * Restarts the BGP KeepaliveTimer.
+ */
+ private void restartKeepaliveTimer(ChannelHandlerContext ctx) {
+ if (localKeepaliveInterval == 0) {
+ return; // Nothing to do
+ }
+ keepaliveTimeout = timer.newTimeout(new TransmitKeepaliveTask(ctx),
+ localKeepaliveInterval,
+ TimeUnit.SECONDS);
+ }
+
+ /**
+ * Task class for transmitting KEEPALIVE messages.
+ */
+ private final class TransmitKeepaliveTask implements TimerTask {
+ private final ChannelHandlerContext ctx;
+
+ /**
+ * Constructor for given Channel Handler Context.
+ *
+ * @param ctx the Channel Handler Context to use
+ */
+ TransmitKeepaliveTask(ChannelHandlerContext ctx) {
+ this.ctx = ctx;
+ }
+
+ @Override
+ public void run(Timeout timeout) throws Exception {
+ if (timeout.isCancelled()) {
+ return;
+ }
+ if (!ctx.getChannel().isOpen()) {
+ return;
+ }
+
+ // Transmit the KEEPALIVE
+ ChannelBuffer txMessage = prepareBgpKeepalive();
+ ctx.getChannel().write(txMessage);
+
+ // Restart the KEEPALIVE timer
+ restartKeepaliveTimer(ctx);
+ }
+ }
+
+ /**
+ * Restarts the BGP Session Timeout Timer.
+ */
+ private void restartSessionTimeoutTimer(ChannelHandlerContext ctx) {
+ if (remoteHoldtime == 0) {
+ return; // Nothing to do
+ }
+ if (sessionTimeout != null) {
+ sessionTimeout.cancel();
+ }
+ sessionTimeout = timer.newTimeout(new SessionTimeoutTask(ctx),
+ remoteHoldtime,
+ TimeUnit.SECONDS);
+ }
+
+ /**
+ * Task class for BGP Session timeout.
+ */
+ private final class SessionTimeoutTask implements TimerTask {
+ private final ChannelHandlerContext ctx;
+
+ /**
+ * Constructor for given Channel Handler Context.
+ *
+ * @param ctx the Channel Handler Context to use
+ */
+ SessionTimeoutTask(ChannelHandlerContext ctx) {
+ this.ctx = ctx;
+ }
+
+ @Override
+ public void run(Timeout timeout) throws Exception {
+ if (timeout.isCancelled()) {
+ return;
+ }
+ if (!ctx.getChannel().isOpen()) {
+ return;
+ }
+
+ log.debug("BGP Session Timeout: peer {}", remoteAddress);
+ //
+ // ERROR: Invalid Optional Parameter Length field: Unspecific
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = HoldTimerExpired.ERROR_CODE;
+ int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC;
+ ChannelBuffer txMessage =
+ prepareBgpNotification(errorCode, errorSubcode, null);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+ }
+
+ /**
+ * An exception indicating a parsing error of the BGP message.
+ */
+ private static class BgpParseException extends Exception {
+ /**
+ * Default constructor.
+ */
+ public BgpParseException() {
+ super();
+ }
+
+ /**
+ * Constructor for a specific exception details message.
+ *
+ * @param message the message with the exception details
+ */
+ public BgpParseException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
new file mode 100644
index 0000000..097c002
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
@@ -0,0 +1,355 @@
+package org.onlab.onos.sdnip.bgp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelException;
+import org.jboss.netty.channel.ChannelFactory;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.onlab.onos.sdnip.RouteListener;
+import org.onlab.onos.sdnip.RouteUpdate;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * BGP Session Manager class.
+ */
+public class BgpSessionManager {
+ private static final Logger log =
+ LoggerFactory.getLogger(BgpSessionManager.class);
+ private Channel serverChannel; // Listener for incoming BGP connections
+ private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
+ new ConcurrentHashMap<>();
+ private IpAddress myBgpId; // Same BGP ID for all peers
+
+ private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
+ private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRoutes =
+ new ConcurrentHashMap<>();
+
+ private final RouteListener routeListener;
+
+ /**
+ * Constructor for given route listener.
+ *
+ * @param routeListener the route listener to use
+ */
+ public BgpSessionManager(RouteListener routeListener) {
+ this.routeListener = checkNotNull(routeListener);
+ }
+
+ /**
+ * Gets the BGP sessions.
+ *
+ * @return the BGP sessions
+ */
+ public Collection<BgpSession> getBgpSessions() {
+ return bgpSessions.values();
+ }
+
+ /**
+ * Gets the BGP routes.
+ *
+ * @return the BGP routes
+ */
+ public Collection<BgpRouteEntry> getBgpRoutes() {
+ return bgpRoutes.values();
+ }
+
+ /**
+ * Processes the connection from a BGP peer.
+ *
+ * @param bgpSession the BGP session for the peer
+ * @return true if the connection can be established, otherwise false
+ */
+ boolean peerConnected(BgpSession bgpSession) {
+
+ // Test whether there is already a session from the same remote
+ if (bgpSessions.get(bgpSession.getRemoteAddress()) != null) {
+ return false; // Duplicate BGP session
+ }
+ bgpSessions.put(bgpSession.getRemoteAddress(), bgpSession);
+
+ //
+ // If the first connection, set my BGP ID to the local address
+ // of the socket.
+ //
+ if (bgpSession.getLocalAddress() instanceof InetSocketAddress) {
+ InetAddress inetAddr =
+ ((InetSocketAddress) bgpSession.getLocalAddress()).getAddress();
+ IpAddress ip4Address = IpAddress.valueOf(inetAddr.getAddress());
+ updateMyBgpId(ip4Address);
+ }
+ return true;
+ }
+
+ /**
+ * Processes the disconnection from a BGP peer.
+ *
+ * @param bgpSession the BGP session for the peer
+ */
+ void peerDisconnected(BgpSession bgpSession) {
+ bgpSessions.remove(bgpSession.getRemoteAddress());
+ }
+
+ /**
+ * Conditionally updates the local BGP ID if it wasn't set already.
+ * <p/>
+ * NOTE: A BGP instance should use same BGP ID across all BGP sessions.
+ *
+ * @param ip4Address the IPv4 address to use as BGP ID
+ */
+ private synchronized void updateMyBgpId(IpAddress ip4Address) {
+ if (myBgpId == null) {
+ myBgpId = ip4Address;
+ log.debug("BGP: My BGP ID is {}", myBgpId);
+ }
+ }
+
+ /**
+ * Gets the local BGP Identifier as an IPv4 address.
+ *
+ * @return the local BGP Identifier as an IPv4 address
+ */
+ IpAddress getMyBgpId() {
+ return myBgpId;
+ }
+
+ /**
+ * Gets the BGP Route Selector.
+ *
+ * @return the BGP Route Selector
+ */
+ BgpRouteSelector getBgpRouteSelector() {
+ return bgpRouteSelector;
+ }
+
+ /**
+ * Starts up BGP Session Manager operation.
+ *
+ * @param listenPortNumber the port number to listen on. By default
+ * it should be BgpConstants.BGP_PORT (179)
+ */
+ public void startUp(int listenPortNumber) {
+ log.debug("BGP Session Manager startUp()");
+
+ ChannelFactory channelFactory =
+ new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
+ Executors.newCachedThreadPool());
+ ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
+ @Override
+ public ChannelPipeline getPipeline() throws Exception {
+ // Allocate a new session per connection
+ BgpSession bgpSessionHandler =
+ new BgpSession(BgpSessionManager.this);
+ BgpFrameDecoder bgpFrameDecoder =
+ new BgpFrameDecoder(bgpSessionHandler);
+
+ // Setup the processing pipeline
+ ChannelPipeline pipeline = Channels.pipeline();
+ pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder);
+ pipeline.addLast("BgpSession", bgpSessionHandler);
+ return pipeline;
+ }
+ };
+ InetSocketAddress listenAddress =
+ new InetSocketAddress(listenPortNumber);
+
+ ServerBootstrap serverBootstrap = new ServerBootstrap(channelFactory);
+ // serverBootstrap.setOptions("reuseAddr", true);
+ serverBootstrap.setOption("child.keepAlive", true);
+ serverBootstrap.setOption("child.tcpNoDelay", true);
+ serverBootstrap.setPipelineFactory(pipelineFactory);
+ try {
+ serverChannel = serverBootstrap.bind(listenAddress);
+ } catch (ChannelException e) {
+ log.debug("Exception binding to BGP port {}: ",
+ listenAddress.getPort(), e);
+ }
+ }
+
+ /**
+ * Shuts down the BGP Session Manager operation.
+ */
+ public void shutDown() {
+ // TODO: Complete the implementation: remove routes, etc.
+ if (serverChannel != null) {
+ serverChannel.close();
+ }
+ }
+
+ /**
+ * Class to receive and process the BGP routes from each BGP Session/Peer.
+ */
+ class BgpRouteSelector {
+ /**
+ * Processes route entry updates: added/updated and deleted route
+ * entries.
+ *
+ * @param bgpSession the BGP session the route entry updates were
+ * received on
+ * @param addedBgpRouteEntries the added/updated route entries to
+ * process
+ * @param deletedBgpRouteEntries the deleted route entries to process
+ */
+ synchronized void routeUpdates(BgpSession bgpSession,
+ Collection<BgpRouteEntry> addedBgpRouteEntries,
+ Collection<BgpRouteEntry> deletedBgpRouteEntries) {
+ //
+ // TODO: Merge the updates from different BGP Peers,
+ // by choosing the best route.
+ //
+
+ // Process the deleted route entries
+ for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
+ processDeletedRoute(bgpSession, bgpRouteEntry);
+ }
+
+ // Process the added/updated route entries
+ for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
+ processAddedRoute(bgpSession, bgpRouteEntry);
+ }
+ }
+
+ /**
+ * Processes an added/updated route entry.
+ *
+ * @param bgpSession the BGP session the route entry update was
+ * received on
+ * @param bgpRouteEntry the added/updated route entry
+ */
+ private void processAddedRoute(BgpSession bgpSession,
+ BgpRouteEntry bgpRouteEntry) {
+ RouteUpdate routeUpdate;
+ BgpRouteEntry bestBgpRouteEntry =
+ bgpRoutes.get(bgpRouteEntry.prefix());
+
+ //
+ // Install the new route entry if it is better than the
+ // current best route.
+ //
+ if ((bestBgpRouteEntry == null) ||
+ bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
+ bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry);
+ routeUpdate =
+ new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
+ // Forward the result route updates to the Route Listener
+ routeListener.update(routeUpdate);
+ return;
+ }
+
+ //
+ // If the route entry arrived on the same BGP Session as
+ // the current best route, then elect the next best route
+ // and install it.
+ //
+ if (bestBgpRouteEntry.getBgpSession() !=
+ bgpRouteEntry.getBgpSession()) {
+ return;
+ }
+
+ // Find the next best route
+ bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+ if (bestBgpRouteEntry == null) {
+ //
+ // TODO: Shouldn't happen. Install the new route as a
+ // pre-caution.
+ //
+ log.debug("BGP next best route for prefix {} is missing. " +
+ "Adding the route that is currently processed.",
+ bgpRouteEntry.prefix());
+ bestBgpRouteEntry = bgpRouteEntry;
+ }
+ // Install the next best route
+ bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry);
+ routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+ bestBgpRouteEntry);
+ // Forward the result route updates to the Route Listener
+ routeListener.update(routeUpdate);
+ }
+
+ /**
+ * Processes a deleted route entry.
+ *
+ * @param bgpSession the BGP session the route entry update was
+ * received on
+ * @param bgpRouteEntry the deleted route entry
+ */
+ private void processDeletedRoute(BgpSession bgpSession,
+ BgpRouteEntry bgpRouteEntry) {
+ RouteUpdate routeUpdate;
+ BgpRouteEntry bestBgpRouteEntry =
+ bgpRoutes.get(bgpRouteEntry.prefix());
+
+ //
+ // Remove the route entry only if it was the best one.
+ // Install the the next best route if it exists.
+ //
+ // NOTE: We intentionally use "==" instead of method equals(),
+ // because we need to check whether this is same object.
+ //
+ if (bgpRouteEntry != bestBgpRouteEntry) {
+ return; // Nothing to do
+ }
+
+ //
+ // Find the next best route
+ //
+ bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+ if (bestBgpRouteEntry != null) {
+ // Install the next best route
+ bgpRoutes.put(bestBgpRouteEntry.prefix(),
+ bestBgpRouteEntry);
+ routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+ bestBgpRouteEntry);
+ // Forward the result route updates to the Route Listener
+ routeListener.update(routeUpdate);
+ return;
+ }
+
+ //
+ // No route found. Remove the route entry
+ //
+ bgpRoutes.remove(bgpRouteEntry.prefix());
+ routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
+ bgpRouteEntry);
+ // Forward the result route updates to the Route Listener
+ routeListener.update(routeUpdate);
+ }
+
+ /**
+ * Finds the best route entry among all BGP Sessions.
+ *
+ * @param prefix the prefix of the route
+ * @return the best route if found, otherwise null
+ */
+ private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) {
+ BgpRouteEntry bestRoute = null;
+
+ // Iterate across all BGP Sessions and select the best route
+ for (BgpSession bgpSession : bgpSessions.values()) {
+ BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix);
+ if (route == null) {
+ continue;
+ }
+ if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
+ bestRoute = route;
+ }
+ }
+ return bestRoute;
+ }
+ }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/package-info.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/package-info.java
new file mode 100644
index 0000000..e39d7e0
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Implementation of the BGP protocol.
+ */
+package org.onlab.onos.sdnip.bgp;
\ No newline at end of file
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouteEntryTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouteEntryTest.java
new file mode 100644
index 0000000..45371f7
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouteEntryTest.java
@@ -0,0 +1,143 @@
+package org.onlab.onos.sdnip;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+/**
+ * Unit tests for the RouteEntry class.
+ */
+public class RouteEntryTest {
+ /**
+ * Tests valid class constructor.
+ */
+ @Test
+ public void testConstructor() {
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+
+ RouteEntry routeEntry = new RouteEntry(prefix, nextHop);
+ assertThat(routeEntry.toString(),
+ is("RouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8}"));
+ }
+
+ /**
+ * Tests invalid class constructor for null IPv4 prefix.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullPrefix() {
+ IpPrefix prefix = null;
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+
+ new RouteEntry(prefix, nextHop);
+ }
+
+ /**
+ * Tests invalid class constructor for null IPv4 next-hop.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullNextHop() {
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = null;
+
+ new RouteEntry(prefix, nextHop);
+ }
+
+ /**
+ * Tests getting the fields of a route entry.
+ */
+ @Test
+ public void testGetFields() {
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+
+ RouteEntry routeEntry = new RouteEntry(prefix, nextHop);
+ assertThat(routeEntry.prefix(), is(prefix));
+ assertThat(routeEntry.nextHop(), is(nextHop));
+ }
+
+ /**
+ * Tests creating a binary string from IPv4 prefix.
+ */
+ @Test
+ public void testCreateBinaryString() {
+ IpPrefix prefix;
+
+ prefix = IpPrefix.valueOf("0.0.0.0/0");
+ assertThat(RouteEntry.createBinaryString(prefix), is(""));
+
+ prefix = IpPrefix.valueOf("192.168.166.0/22");
+ assertThat(RouteEntry.createBinaryString(prefix),
+ is("1100000010101000101001"));
+
+ prefix = IpPrefix.valueOf("192.168.166.0/23");
+ assertThat(RouteEntry.createBinaryString(prefix),
+ is("11000000101010001010011"));
+
+ prefix = IpPrefix.valueOf("192.168.166.0/24");
+ assertThat(RouteEntry.createBinaryString(prefix),
+ is("110000001010100010100110"));
+
+ prefix = IpPrefix.valueOf("130.162.10.1/25");
+ assertThat(RouteEntry.createBinaryString(prefix),
+ is("1000001010100010000010100"));
+
+ prefix = IpPrefix.valueOf("255.255.255.255/32");
+ assertThat(RouteEntry.createBinaryString(prefix),
+ is("11111111111111111111111111111111"));
+ }
+
+ /**
+ * Tests equality of {@link RouteEntry}.
+ */
+ @Test
+ public void testEquality() {
+ IpPrefix prefix1 = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop1 = IpAddress.valueOf("5.6.7.8");
+ RouteEntry routeEntry1 = new RouteEntry(prefix1, nextHop1);
+
+ IpPrefix prefix2 = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop2 = IpAddress.valueOf("5.6.7.8");
+ RouteEntry routeEntry2 = new RouteEntry(prefix2, nextHop2);
+
+ assertThat(routeEntry1, is(routeEntry2));
+ }
+
+ /**
+ * Tests non-equality of {@link RouteEntry}.
+ */
+ @Test
+ public void testNonEquality() {
+ IpPrefix prefix1 = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop1 = IpAddress.valueOf("5.6.7.8");
+ RouteEntry routeEntry1 = new RouteEntry(prefix1, nextHop1);
+
+ IpPrefix prefix2 = IpPrefix.valueOf("1.2.3.0/25"); // Different
+ IpAddress nextHop2 = IpAddress.valueOf("5.6.7.8");
+ RouteEntry routeEntry2 = new RouteEntry(prefix2, nextHop2);
+
+ IpPrefix prefix3 = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop3 = IpAddress.valueOf("5.6.7.9"); // Different
+ RouteEntry routeEntry3 = new RouteEntry(prefix3, nextHop3);
+
+ assertThat(routeEntry1, is(not(routeEntry2)));
+ assertThat(routeEntry1, is(not(routeEntry3)));
+ }
+
+ /**
+ * Tests object string representation.
+ */
+ @Test
+ public void testToString() {
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+ RouteEntry routeEntry = new RouteEntry(prefix, nextHop);
+
+ assertThat(routeEntry.toString(),
+ is("RouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8}"));
+ }
+}
diff --git a/cli/pom.xml b/cli/pom.xml
index cc1d9dd..8a11f34 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -26,6 +26,16 @@
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-osgi</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
diff --git a/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java b/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
index 184a7e6..839d2841 100644
--- a/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
@@ -1,5 +1,6 @@
package org.onlab.onos.cli;
+import org.apache.karaf.shell.commands.Option;
import org.apache.karaf.shell.console.OsgiCommandSupport;
import org.onlab.osgi.DefaultServiceDirectory;
import org.onlab.osgi.ServiceNotFoundException;
@@ -9,6 +10,10 @@
*/
public abstract class AbstractShellCommand extends OsgiCommandSupport {
+ @Option(name = "-j", aliases = "--json", description = "Output JSON",
+ required = false, multiValued = false)
+ private boolean json = false;
+
/**
* Returns the reference to the implementation of the specified service.
*
@@ -46,6 +51,15 @@
*/
protected abstract void execute();
+ /**
+ * Indicates whether JSON format should be output.
+ *
+ * @return true if JSON is requested
+ */
+ protected boolean outputJson() {
+ return json;
+ }
+
@Override
protected Object doExecute() throws Exception {
try {
diff --git a/cli/src/main/java/org/onlab/onos/cli/MastersListCommand.java b/cli/src/main/java/org/onlab/onos/cli/MastersListCommand.java
index 0f7857f..fff4955 100644
--- a/cli/src/main/java/org/onlab/onos/cli/MastersListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/MastersListCommand.java
@@ -1,7 +1,9 @@
package org.onlab.onos.cli;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.collect.Lists;
-
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cluster.ClusterService;
import org.onlab.onos.cluster.ControllerNode;
@@ -26,15 +28,50 @@
MastershipService mastershipService = get(MastershipService.class);
List<ControllerNode> nodes = newArrayList(service.getNodes());
Collections.sort(nodes, Comparators.NODE_COMPARATOR);
+
+ if (outputJson()) {
+ print("%s", json(service, mastershipService, nodes));
+ } else {
+ for (ControllerNode node : nodes) {
+ List<DeviceId> ids = Lists.newArrayList(mastershipService.getDevicesOf(node.id()));
+ Collections.sort(ids, Comparators.ELEMENT_ID_COMPARATOR);
+ print("%s: %d devices", node.id(), ids.size());
+ for (DeviceId deviceId : ids) {
+ print(" %s", deviceId);
+ }
+ }
+ }
+ }
+
+ // Produces JSON structure.
+ private JsonNode json(ClusterService service, MastershipService mastershipService,
+ List<ControllerNode> nodes) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
ControllerNode self = service.getLocalNode();
for (ControllerNode node : nodes) {
List<DeviceId> ids = Lists.newArrayList(mastershipService.getDevicesOf(node.id()));
- Collections.sort(ids, Comparators.ELEMENT_ID_COMPARATOR);
- print("%s: %d devices", node.id(), ids.size());
- for (DeviceId deviceId : ids) {
- print(" %s", deviceId);
- }
+ result.add(mapper.createObjectNode()
+ .put("id", node.id().toString())
+ .put("size", ids.size())
+ .set("devices", json(mapper, ids)));
}
+ return result;
+ }
+
+ /**
+ * Produces a JSON array containing the specified device identifiers.
+ *
+ * @param mapper object mapper
+ * @param ids collection of device identifiers
+ * @return JSON array
+ */
+ public static JsonNode json(ObjectMapper mapper, Iterable<DeviceId> ids) {
+ ArrayNode result = mapper.createArrayNode();
+ for (DeviceId deviceId : ids) {
+ result.add(deviceId.toString());
+ }
+ return result;
}
}
diff --git a/cli/src/main/java/org/onlab/onos/cli/NodesListCommand.java b/cli/src/main/java/org/onlab/onos/cli/NodesListCommand.java
index b7b4556..d9bdf94 100644
--- a/cli/src/main/java/org/onlab/onos/cli/NodesListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/NodesListCommand.java
@@ -1,5 +1,8 @@
package org.onlab.onos.cli;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cluster.ClusterService;
import org.onlab.onos.cluster.ControllerNode;
@@ -24,12 +27,32 @@
ClusterService service = get(ClusterService.class);
List<ControllerNode> nodes = newArrayList(service.getNodes());
Collections.sort(nodes, Comparators.NODE_COMPARATOR);
+ if (outputJson()) {
+ print("%s", json(service, nodes));
+ } else {
+ ControllerNode self = service.getLocalNode();
+ for (ControllerNode node : nodes) {
+ print(FMT, node.id(), node.ip(), node.tcpPort(),
+ service.getState(node.id()),
+ node.equals(self) ? "*" : "");
+ }
+ }
+ }
+
+ // Produces JSON structure.
+ private JsonNode json(ClusterService service, List<ControllerNode> nodes) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
ControllerNode self = service.getLocalNode();
for (ControllerNode node : nodes) {
- print(FMT, node.id(), node.ip(), node.tcpPort(),
- service.getState(node.id()),
- node.equals(self) ? "*" : "");
+ result.add(mapper.createObjectNode()
+ .put("id", node.id().toString())
+ .put("ip", node.ip().toString())
+ .put("tcpPort", node.tcpPort())
+ .put("state", service.getState(node.id()).toString())
+ .put("self", node.equals(self)));
}
+ return result;
}
}
diff --git a/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java b/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java
index 1597b55..180405b 100644
--- a/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java
@@ -1,5 +1,6 @@
package org.onlab.onos.cli;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.CoreService;
import org.onlab.onos.cluster.ClusterService;
@@ -22,18 +23,32 @@
protected void execute() {
TopologyService topologyService = get(TopologyService.class);
Topology topology = topologyService.currentTopology();
- print("node=%s, version=%s",
- get(ClusterService.class).getLocalNode().ip(),
- get(CoreService.class).version().toString());
- print("nodes=%d, devices=%d, links=%d, hosts=%d, clusters=%s, paths=%d, flows=%d, intents=%d",
- get(ClusterService.class).getNodes().size(),
- get(DeviceService.class).getDeviceCount(),
- get(LinkService.class).getLinkCount(),
- get(HostService.class).getHostCount(),
- topologyService.getClusters(topology).size(),
- topology.pathCount(),
- get(FlowRuleService.class).getFlowRuleCount(),
- get(IntentService.class).getIntentCount());
+ if (outputJson()) {
+ print("%s", new ObjectMapper().createObjectNode()
+ .put("node", get(ClusterService.class).getLocalNode().ip().toString())
+ .put("version", get(CoreService.class).version().toString())
+ .put("nodes", get(ClusterService.class).getNodes().size())
+ .put("devices", get(DeviceService.class).getDeviceCount())
+ .put("links", get(LinkService.class).getLinkCount())
+ .put("hosts", get(HostService.class).getHostCount())
+ .put("clusters", topologyService.getClusters(topology).size())
+ .put("paths", topology.pathCount())
+ .put("flows", get(FlowRuleService.class).getFlowRuleCount())
+ .put("intents", get(IntentService.class).getIntentCount()));
+ } else {
+ print("node=%s, version=%s",
+ get(ClusterService.class).getLocalNode().ip(),
+ get(CoreService.class).version().toString());
+ print("nodes=%d, devices=%d, links=%d, hosts=%d, clusters=%s, paths=%d, flows=%d, intents=%d",
+ get(ClusterService.class).getNodes().size(),
+ get(DeviceService.class).getDeviceCount(),
+ get(LinkService.class).getLinkCount(),
+ get(HostService.class).getHostCount(),
+ topologyService.getClusters(topology).size(),
+ topology.pathCount(),
+ get(FlowRuleService.class).getFlowRuleCount(),
+ get(IntentService.class).getIntentCount());
+ }
}
}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/ClusterDevicesCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/ClusterDevicesCommand.java
index f09b185..e03af45 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/ClusterDevicesCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/ClusterDevicesCommand.java
@@ -1,5 +1,6 @@
package org.onlab.onos.cli.net;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
@@ -10,6 +11,7 @@
import java.util.Collections;
import java.util.List;
+import static org.onlab.onos.cli.MastersListCommand.json;
import static org.onlab.onos.net.topology.ClusterId.clusterId;
/**
@@ -33,11 +35,14 @@
} else {
List<DeviceId> ids = Lists.newArrayList(service.getClusterDevices(topology, cluster));
Collections.sort(ids, Comparators.ELEMENT_ID_COMPARATOR);
- for (DeviceId deviceId : ids) {
- print("%s", deviceId);
+ if (outputJson()) {
+ print("%s", json(new ObjectMapper(), ids));
+ } else {
+ for (DeviceId deviceId : ids) {
+ print("%s", deviceId);
+ }
}
}
}
-
}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/ClusterLinksCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/ClusterLinksCommand.java
index 2bbfb46..ed5be77 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/ClusterLinksCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/ClusterLinksCommand.java
@@ -5,6 +5,7 @@
import org.onlab.onos.net.Link;
import org.onlab.onos.net.topology.TopologyCluster;
+import static org.onlab.onos.cli.net.LinksListCommand.json;
import static org.onlab.onos.cli.net.LinksListCommand.linkString;
import static org.onlab.onos.net.topology.ClusterId.clusterId;
@@ -26,6 +27,8 @@
TopologyCluster cluster = service.getCluster(topology, clusterId(cid));
if (cluster == null) {
error("No such cluster %s", cid);
+ } else if (outputJson()) {
+ print("%s", json(service.getClusterLinks(topology, cluster)));
} else {
for (Link link : service.getClusterLinks(topology, cluster)) {
print(linkString(link));
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/ClustersListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/ClustersListCommand.java
index 2b2953b..f41f85e 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/ClustersListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/ClustersListCommand.java
@@ -1,5 +1,8 @@
package org.onlab.onos.cli.net;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.collect.Lists;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.Comparators;
@@ -24,9 +27,26 @@
List<TopologyCluster> clusters = Lists.newArrayList(service.getClusters(topology));
Collections.sort(clusters, Comparators.CLUSTER_COMPARATOR);
- for (TopologyCluster cluster : clusters) {
- print(FMT, cluster.id().index(), cluster.deviceCount(), cluster.linkCount());
+ if (outputJson()) {
+ print("%s", json(clusters));
+ } else {
+ for (TopologyCluster cluster : clusters) {
+ print(FMT, cluster.id().index(), cluster.deviceCount(), cluster.linkCount());
+ }
}
}
+ // Produces a JSON result.
+ private JsonNode json(Iterable<TopologyCluster> clusters) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ for (TopologyCluster cluster : clusters) {
+ result.add(mapper.createObjectNode()
+ .put("id", cluster.id().index())
+ .put("deviceCount", cluster.deviceCount())
+ .put("linkCount", cluster.linkCount()));
+ }
+ return result;
+ }
+
}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
index f66cedd..32b7830 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
@@ -1,7 +1,12 @@
package org.onlab.onos.cli.net;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
import org.onlab.onos.cli.Comparators;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.Port;
@@ -22,6 +27,14 @@
private static final String FMT = " port=%s, state=%s";
+ @Option(name = "-e", aliases = "--enabled", description = "Show only enabled ports",
+ required = false, multiValued = false)
+ private boolean enabled = false;
+
+ @Option(name = "-d", aliases = "--disabled", description = "Show only disabled ports",
+ required = false, multiValued = false)
+ private boolean disabled = false;
+
@Argument(index = 0, name = "uri", description = "Device ID",
required = false, multiValued = false)
String uri = null;
@@ -30,26 +43,78 @@
protected void execute() {
DeviceService service = get(DeviceService.class);
if (uri == null) {
- for (Device device : getSortedDevices(service)) {
- printDevice(service, device);
+ if (outputJson()) {
+ print("%s", jsonPorts(service, getSortedDevices(service)));
+ } else {
+ for (Device device : getSortedDevices(service)) {
+ printDevice(service, device);
+ }
}
+
} else {
Device device = service.getDevice(deviceId(uri));
if (device == null) {
error("No such device %s", uri);
+ } else if (outputJson()) {
+ print("%s", jsonPorts(service, new ObjectMapper(), device));
} else {
printDevice(service, device);
}
}
}
+ /**
+ * Produces JSON array containing ports of the specified devices.
+ *
+ * @param service device service
+ * @param devices collection of devices
+ * @return JSON array
+ */
+ public JsonNode jsonPorts(DeviceService service, Iterable<Device> devices) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ for (Device device : devices) {
+ result.add(jsonPorts(service, mapper, device));
+ }
+ return result;
+ }
+
+ /**
+ * Produces JSON array containing ports of the specified device.
+ *
+ * @param service device service
+ * @param mapper object mapper
+ * @param device infrastructure devices
+ * @return JSON array
+ */
+ public JsonNode jsonPorts(DeviceService service, ObjectMapper mapper, Device device) {
+ ObjectNode result = mapper.createObjectNode();
+ ArrayNode ports = mapper.createArrayNode();
+ for (Port port : service.getPorts(device.id())) {
+ if (isIncluded(port)) {
+ ports.add(mapper.createObjectNode()
+ .put("port", port.number().toString())
+ .put("isEnabled", port.isEnabled()));
+ }
+ }
+ return result.put("device", device.id().toString()).set("ports", ports);
+ }
+
+ // Determines if a port should be included in output.
+ private boolean isIncluded(Port port) {
+ return enabled && port.isEnabled() || disabled && !port.isEnabled() ||
+ !enabled && !disabled;
+ }
+
@Override
protected void printDevice(DeviceService service, Device device) {
super.printDevice(service, device);
List<Port> ports = new ArrayList<>(service.getPorts(device.id()));
Collections.sort(ports, Comparators.PORT_COMPARATOR);
for (Port port : ports) {
- print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled");
+ if (isIncluded(port)) {
+ print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled");
+ }
}
}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
index f34f97e..b7a8acc 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
@@ -1,5 +1,9 @@
package org.onlab.onos.cli.net;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.cli.Comparators;
@@ -24,12 +28,55 @@
@Override
protected void execute() {
DeviceService service = get(DeviceService.class);
- for (Device device : getSortedDevices(service)) {
- printDevice(service, device);
+ if (outputJson()) {
+ print("%s", json(service, getSortedDevices(service)));
+ } else {
+ for (Device device : getSortedDevices(service)) {
+ printDevice(service, device);
+ }
}
}
/**
+ * Returns JSON node representing the specified devices.
+ *
+ * @param service device service
+ * @param devices collection of devices
+ * @return JSON node
+ */
+ public static JsonNode json(DeviceService service, Iterable<Device> devices) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ for (Device device : devices) {
+ result.add(json(service, mapper, device));
+ }
+ return result;
+ }
+
+ /**
+ * Returns JSON node representing the specified device.
+ *
+ * @param service device service
+ * @param mapper object mapper
+ * @param device infrastructure device
+ * @return JSON node
+ */
+ public static ObjectNode json(DeviceService service, ObjectMapper mapper,
+ Device device) {
+ ObjectNode result = mapper.createObjectNode();
+ if (device != null) {
+ result.put("id", device.id().toString())
+ .put("available", service.isAvailable(device.id()))
+ .put("role", service.getRole(device.id()).toString())
+ .put("mfr", device.manufacturer())
+ .put("hw", device.hwVersion())
+ .put("sw", device.swVersion())
+ .put("serial", device.serialNumber());
+ }
+ return result;
+ }
+
+ /**
* Returns the list of devices sorted using the device ID URIs.
*
* @param service device service
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/FlowsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/FlowsListCommand.java
index 28309c5..9291212 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/FlowsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/FlowsListCommand.java
@@ -1,5 +1,9 @@
package org.onlab.onos.cli.net;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Maps;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
@@ -12,6 +16,8 @@
import org.onlab.onos.net.flow.FlowEntry;
import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
import org.onlab.onos.net.flow.FlowRuleService;
+import org.onlab.onos.net.flow.criteria.Criterion;
+import org.onlab.onos.net.flow.instructions.Instruction;
import java.util.Collections;
import java.util.List;
@@ -48,12 +54,76 @@
DeviceService deviceService = get(DeviceService.class);
FlowRuleService service = get(FlowRuleService.class);
Map<Device, List<FlowEntry>> flows = getSortedFlows(deviceService, service);
- for (Device d : getSortedDevices(deviceService)) {
- printFlows(d, flows.get(d), coreService);
+
+ if (outputJson()) {
+ print("%s", json(coreService, getSortedDevices(deviceService), flows));
+ } else {
+ for (Device d : getSortedDevices(deviceService)) {
+ printFlows(d, flows.get(d), coreService);
+ }
}
}
/**
+ * Produces a JSON array of flows grouped by the each device.
+ *
+ * @param coreService core service
+ * @param devices collection of devices to group flow by
+ * @param flows collection of flows per each device
+ * @return JSON array
+ */
+ private JsonNode json(CoreService coreService, Iterable<Device> devices,
+ Map<Device, List<FlowEntry>> flows) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ for (Device device : devices) {
+ result.add(json(coreService, mapper, device, flows.get(device)));
+ }
+ return result;
+ }
+
+ // Produces JSON object with the flows of the given device.
+ private ObjectNode json(CoreService coreService, ObjectMapper mapper,
+ Device device, List<FlowEntry> flows) {
+ ObjectNode result = mapper.createObjectNode();
+ ArrayNode array = mapper.createArrayNode();
+
+ for (FlowEntry flow : flows) {
+ array.add(json(coreService, mapper, flow));
+ }
+
+ result.put("device", device.id().toString())
+ .put("flowCount", flows.size())
+ .put("flows", array);
+ return result;
+ }
+
+ // Produces JSON structure with the specified flow data.
+ private ObjectNode json(CoreService coreService, ObjectMapper mapper,
+ FlowEntry flow) {
+ ObjectNode result = mapper.createObjectNode();
+ ArrayNode crit = mapper.createArrayNode();
+ for (Criterion c : flow.selector().criteria()) {
+ crit.add(c.toString());
+ }
+
+ ArrayNode instr = mapper.createArrayNode();
+ for (Instruction i : flow.treatment().instructions()) {
+ instr.add(i.toString());
+ }
+
+ result.put("flowId", Long.toHexString(flow.id().value()))
+ .put("state", flow.state().toString())
+ .put("bytes", flow.bytes())
+ .put("packets", flow.packets())
+ .put("life", flow.life())
+ .put("appId", coreService.getAppId(flow.appId()).name());
+ result.set("selector", crit);
+ result.set("treatment", instr);
+ return result;
+ }
+
+ /**
* Returns the list of devices sorted using the device ID URIs.
*
* @param service device service
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java
index cd9ba08..f431142 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java
@@ -1,10 +1,15 @@
package org.onlab.onos.cli.net;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.cli.Comparators;
import org.onlab.onos.net.Host;
import org.onlab.onos.net.host.HostService;
+import org.onlab.packet.IpPrefix;
import java.util.Collections;
import java.util.List;
@@ -15,7 +20,7 @@
* Lists all currently-known hosts.
*/
@Command(scope = "onos", name = "hosts",
- description = "Lists all currently-known hosts.")
+ description = "Lists all currently-known hosts.")
public class HostsListCommand extends AbstractShellCommand {
private static final String FMT =
@@ -24,11 +29,42 @@
@Override
protected void execute() {
HostService service = get(HostService.class);
- for (Host host : getSortedHosts(service)) {
- printHost(host);
+ if (outputJson()) {
+ print("%s", json(getSortedHosts(service)));
+ } else {
+ for (Host host : getSortedHosts(service)) {
+ printHost(host);
+ }
}
}
+ // Produces JSON structure.
+ private static JsonNode json(Iterable<Host> hosts) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ for (Host host : hosts) {
+ result.add(json(mapper, host));
+ }
+ return result;
+ }
+
+ // Produces JSON structure.
+ private static JsonNode json(ObjectMapper mapper, Host host) {
+ ObjectNode loc = LinksListCommand.json(mapper, host.location())
+ .put("time", host.location().time());
+ ArrayNode ips = mapper.createArrayNode();
+ for (IpPrefix ip : host.ipAddresses()) {
+ ips.add(ip.toString());
+ }
+ ObjectNode result = mapper.createObjectNode()
+ .put("id", host.id().toString())
+ .put("mac", host.mac().toString())
+ .put("vlan", host.vlan().toString());
+ result.set("location", loc);
+ result.set("ips", ips);
+ return result;
+ }
+
/**
* Returns the list of devices sorted using the device ID URIs.
*
@@ -44,14 +80,14 @@
/**
* Prints information about a host.
*
- * @param host
+ * @param host end-station host
*/
protected void printHost(Host host) {
if (host != null) {
print(FMT, host.id(), host.mac(),
- host.location().deviceId(),
- host.location().port(),
- host.vlan(), host.ipAddresses());
+ host.location().deviceId(),
+ host.location().port(),
+ host.vlan(), host.ipAddresses());
}
}
- }
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
index f5226b1..1c3287e 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
@@ -1,8 +1,13 @@
package org.onlab.onos.cli.net;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.link.LinkService;
@@ -27,12 +32,58 @@
LinkService service = get(LinkService.class);
Iterable<Link> links = uri != null ?
service.getDeviceLinks(deviceId(uri)) : service.getLinks();
- for (Link link : links) {
- print(linkString(link));
+ if (outputJson()) {
+ print("%s", json(links));
+ } else {
+ for (Link link : links) {
+ print(linkString(link));
+ }
}
}
/**
+ * Produces a JSON array containing the specified links.
+ *
+ * @param links collection of links
+ * @return JSON array
+ */
+ public static JsonNode json(Iterable<Link> links) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ for (Link link : links) {
+ result.add(json(mapper, link));
+ }
+ return result;
+ }
+
+ /**
+ * Produces a JSON object for the specified link.
+ *
+ * @param mapper object mapper
+ * @param link link to encode
+ * @return JSON object
+ */
+ public static ObjectNode json(ObjectMapper mapper, Link link) {
+ ObjectNode result = mapper.createObjectNode();
+ result.set("src", json(mapper, link.src()));
+ result.set("dst", json(mapper, link.dst()));
+ return result;
+ }
+
+ /**
+ * Produces a JSON object for the specified connect point.
+ *
+ * @param mapper object mapper
+ * @param connectPoint connection point to encode
+ * @return JSON object
+ */
+ public static ObjectNode json(ObjectMapper mapper, ConnectPoint connectPoint) {
+ return mapper.createObjectNode()
+ .put("device", connectPoint.deviceId().toString())
+ .put("port", connectPoint.port().toString());
+ }
+
+ /**
* Returns a formatted string representing the given link.
*
* @param link infrastructure link
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/PathListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/PathListCommand.java
index 8bb808a..6777625 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/PathListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/PathListCommand.java
@@ -1,5 +1,8 @@
package org.onlab.onos.cli.net;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.net.Link;
@@ -32,12 +35,33 @@
protected void execute() {
init();
Set<Path> paths = service.getPaths(topology, deviceId(src), deviceId(dst));
- for (Path path : paths) {
- print(pathString(path));
+ if (outputJson()) {
+ print("%s", json(paths));
+ } else {
+ for (Path path : paths) {
+ print(pathString(path));
+ }
}
}
/**
+ * Produces a JSON array containing the specified paths.
+ *
+ * @param paths collection of paths
+ * @return JSON array
+ */
+ public static JsonNode json(Iterable<Path> paths) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ for (Path path : paths) {
+ result.add(LinksListCommand.json(mapper, path)
+ .put("cost", path.cost())
+ .set("links", LinksListCommand.json(path.links())));
+ }
+ return result;
+ }
+
+ /**
* Produces a formatted string representing the specified path.
*
* @param path network path
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/TopologyCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/TopologyCommand.java
index 5c8310f..a7e6422 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/TopologyCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/TopologyCommand.java
@@ -1,5 +1,6 @@
package org.onlab.onos.cli.net;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.net.topology.Topology;
@@ -30,8 +31,17 @@
@Override
protected void execute() {
init();
- print(FMT, topology.time(), topology.deviceCount(), topology.linkCount(),
- topology.clusterCount(), topology.pathCount());
+ if (outputJson()) {
+ print("%s", new ObjectMapper().createObjectNode()
+ .put("time", topology.time())
+ .put("deviceCount", topology.deviceCount())
+ .put("linkCount", topology.linkCount())
+ .put("clusterCount", topology.clusterCount())
+ .put("pathCount", topology.pathCount()));
+ } else {
+ print(FMT, topology.time(), topology.deviceCount(), topology.linkCount(),
+ topology.clusterCount(), topology.pathCount());
+ }
}
}
diff --git a/core/api/src/main/java/org/onlab/onos/cluster/ControllerNodeToNodeId.java b/core/api/src/main/java/org/onlab/onos/cluster/ControllerNodeToNodeId.java
index 0891494..0f79c68 100644
--- a/core/api/src/main/java/org/onlab/onos/cluster/ControllerNodeToNodeId.java
+++ b/core/api/src/main/java/org/onlab/onos/cluster/ControllerNodeToNodeId.java
@@ -12,7 +12,11 @@
@Override
public NodeId apply(ControllerNode input) {
- return input.id();
+ if (input == null) {
+ return null;
+ } else {
+ return input.id();
+ }
}
/**
diff --git a/core/api/src/main/java/org/onlab/onos/net/host/DefaultHostDescription.java b/core/api/src/main/java/org/onlab/onos/net/host/DefaultHostDescription.java
index 2e92dad..71a952e 100644
--- a/core/api/src/main/java/org/onlab/onos/net/host/DefaultHostDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/host/DefaultHostDescription.java
@@ -1,5 +1,8 @@
package org.onlab.onos.net.host;
+import java.util.Collections;
+import java.util.Set;
+
import org.onlab.onos.net.AbstractDescription;
import org.onlab.onos.net.HostLocation;
import org.onlab.onos.net.SparseAnnotations;
@@ -7,6 +10,8 @@
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
+import com.google.common.collect.ImmutableSet;
+
import static com.google.common.base.MoreObjects.toStringHelper;
/**
@@ -18,7 +23,7 @@
private final MacAddress mac;
private final VlanId vlan;
private final HostLocation location;
- private final IpPrefix ip;
+ private final Set<IpPrefix> ip;
/**
* Creates a host description using the supplied information.
@@ -31,7 +36,7 @@
public DefaultHostDescription(MacAddress mac, VlanId vlan,
HostLocation location,
SparseAnnotations... annotations) {
- this(mac, vlan, location, null, annotations);
+ this(mac, vlan, location, Collections.<IpPrefix>emptySet(), annotations);
}
/**
@@ -46,11 +51,26 @@
public DefaultHostDescription(MacAddress mac, VlanId vlan,
HostLocation location, IpPrefix ip,
SparseAnnotations... annotations) {
+ this(mac, vlan, location, ImmutableSet.of(ip), annotations);
+ }
+
+ /**
+ * Creates a host description using the supplied information.
+ *
+ * @param mac host MAC address
+ * @param vlan host VLAN identifier
+ * @param location host location
+ * @param ip host IP addresses
+ * @param annotations optional key/value annotations map
+ */
+ public DefaultHostDescription(MacAddress mac, VlanId vlan,
+ HostLocation location, Set<IpPrefix> ip,
+ SparseAnnotations... annotations) {
super(annotations);
this.mac = mac;
this.vlan = vlan;
this.location = location;
- this.ip = ip;
+ this.ip = ImmutableSet.copyOf(ip);
}
@Override
@@ -69,7 +89,7 @@
}
@Override
- public IpPrefix ipAddress() {
+ public Set<IpPrefix> ipAddress() {
return ip;
}
diff --git a/core/api/src/main/java/org/onlab/onos/net/host/HostDescription.java b/core/api/src/main/java/org/onlab/onos/net/host/HostDescription.java
index fc16854..258ce3d 100644
--- a/core/api/src/main/java/org/onlab/onos/net/host/HostDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/host/HostDescription.java
@@ -1,5 +1,7 @@
package org.onlab.onos.net.host;
+import java.util.Set;
+
import org.onlab.onos.net.Description;
import org.onlab.onos.net.HostLocation;
import org.onlab.packet.IpPrefix;
@@ -38,6 +40,6 @@
* @return host IP address
*/
// FIXME: Switch to IpAddress
- IpPrefix ipAddress();
+ Set<IpPrefix> ipAddress();
}
diff --git a/core/api/src/test/java/org/onlab/onos/cluster/ControllerNodeToNodeIdTest.java b/core/api/src/test/java/org/onlab/onos/cluster/ControllerNodeToNodeIdTest.java
index 44261e8..b95dcfc 100644
--- a/core/api/src/test/java/org/onlab/onos/cluster/ControllerNodeToNodeIdTest.java
+++ b/core/api/src/test/java/org/onlab/onos/cluster/ControllerNodeToNodeIdTest.java
@@ -1,5 +1,6 @@
package org.onlab.onos.cluster;
+import static com.google.common.base.Predicates.notNull;
import static org.junit.Assert.*;
import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
@@ -30,12 +31,13 @@
@Test
public final void testToNodeId() {
- final Iterable<ControllerNode> nodes = Arrays.asList(CN1, CN2, CN3);
+ final Iterable<ControllerNode> nodes = Arrays.asList(CN1, CN2, CN3, null);
final List<NodeId> nodeIds = Arrays.asList(NID1, NID2, NID3);
assertEquals(nodeIds,
FluentIterable.from(nodes)
.transform(toNodeId())
+ .filter(notNull())
.toList());
}
diff --git a/core/api/src/test/java/org/onlab/onos/net/host/DefualtHostDecriptionTest.java b/core/api/src/test/java/org/onlab/onos/net/host/DefualtHostDecriptionTest.java
index 5ae7c27..f2b9475 100644
--- a/core/api/src/test/java/org/onlab/onos/net/host/DefualtHostDecriptionTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/host/DefualtHostDecriptionTest.java
@@ -8,6 +8,8 @@
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
+import com.google.common.collect.ImmutableSet;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -33,7 +35,7 @@
assertEquals("incorrect mac", MAC, host.hwAddress());
assertEquals("incorrect vlan", VLAN, host.vlan());
assertEquals("incorrect location", LOC, host.location());
- assertEquals("incorrect ip's", IP, host.ipAddress());
+ assertEquals("incorrect ip's", ImmutableSet.of(IP), host.ipAddress());
assertTrue("incorrect toString", host.toString().contains("vlan=10"));
}
diff --git a/core/net/src/main/java/org/onlab/onos/impl/package-info.java b/core/net/src/main/java/org/onlab/onos/impl/package-info.java
index bbe539f..2bf17b3 100644
--- a/core/net/src/main/java/org/onlab/onos/impl/package-info.java
+++ b/core/net/src/main/java/org/onlab/onos/impl/package-info.java
@@ -1,4 +1,4 @@
/**
- *
+ * Miscellaneous core system implementations.
*/
package org.onlab.onos.impl;
\ No newline at end of file
diff --git a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
index 8a86544..cd84dce 100644
--- a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
@@ -355,7 +355,7 @@
arp.setTargetProtocolAddress(((ARP) request.getPayload())
.getSenderProtocolAddress());
- arp.setSenderProtocolAddress(srcIp.toRealInt());
+ arp.setSenderProtocolAddress(srcIp.toInt());
eth.setPayload(arp);
return eth;
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
index d923075..2603da1 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
@@ -515,12 +515,12 @@
Map<PortNumber, Port> ports,
Set<PortNumber> processed) {
List<DeviceEvent> events = new ArrayList<>();
- Iterator<PortNumber> iterator = ports.keySet().iterator();
+ Iterator<Entry<PortNumber, Port>> iterator = ports.entrySet().iterator();
while (iterator.hasNext()) {
- PortNumber portNumber = iterator.next();
+ Entry<PortNumber, Port> e = iterator.next();
+ PortNumber portNumber = e.getKey();
if (!processed.contains(portNumber)) {
- events.add(new DeviceEvent(PORT_REMOVED, device,
- ports.get(portNumber)));
+ events.add(new DeviceEvent(PORT_REMOVED, device, e.getValue()));
iterator.remove();
}
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
index 39bc770..4025d0c 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
@@ -1,10 +1,13 @@
package org.onlab.onos.store.host.impl;
+import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
+import org.apache.commons.lang3.RandomUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@@ -12,6 +15,8 @@
import org.apache.felix.scr.annotations.ReferenceCardinality;
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.Annotations;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultHost;
@@ -19,6 +24,7 @@
import org.onlab.onos.net.Host;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.HostLocation;
+import org.onlab.onos.net.host.DefaultHostDescription;
import org.onlab.onos.net.host.HostClockService;
import org.onlab.onos.net.host.HostDescription;
import org.onlab.onos.net.host.HostEvent;
@@ -42,12 +48,19 @@
import org.slf4j.Logger;
import java.io.IOException;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
import static org.onlab.onos.net.host.HostEvent.Type.*;
+import static org.onlab.util.Tools.namedThreads;
import static org.slf4j.LoggerFactory.getLogger;
//TODO: multi-provider, annotation not supported.
@@ -88,24 +101,58 @@
protected void setupKryoPool() {
serializerPool = KryoPool.newBuilder()
.register(DistributedStoreSerializers.COMMON)
+ .register(InternalHostEvent.class)
.register(InternalHostRemovedEvent.class)
+ .register(HostFragmentId.class)
+ .register(HostAntiEntropyAdvertisement.class)
.build()
.populate(1);
}
};
+ private ScheduledExecutorService executor;
+
@Activate
public void activate() {
clusterCommunicator.addSubscriber(
- GossipHostStoreMessageSubjects.HOST_UPDATED, new InternalHostEventListener());
+ GossipHostStoreMessageSubjects.HOST_UPDATED,
+ new InternalHostEventListener());
clusterCommunicator.addSubscriber(
- GossipHostStoreMessageSubjects.HOST_REMOVED, new InternalHostRemovedEventListener());
+ GossipHostStoreMessageSubjects.HOST_REMOVED,
+ new InternalHostRemovedEventListener());
+ clusterCommunicator.addSubscriber(
+ GossipHostStoreMessageSubjects.HOST_ANTI_ENTROPY_ADVERTISEMENT,
+ new InternalHostAntiEntropyAdvertisementListener());
+
+ executor =
+ newSingleThreadScheduledExecutor(namedThreads("link-anti-entropy-%d"));
+
+ // TODO: Make these configurable
+ long initialDelaySec = 5;
+ long periodSec = 5;
+ // start anti-entropy thread
+ executor.scheduleAtFixedRate(new SendAdvertisementTask(),
+ initialDelaySec, periodSec, TimeUnit.SECONDS);
log.info("Started");
}
@Deactivate
public void deactivate() {
+ executor.shutdownNow();
+ try {
+ if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+ log.error("Timeout during executor shutdown");
+ }
+ } catch (InterruptedException e) {
+ log.error("Error during executor shutdown", e);
+ }
+
+ hosts.clear();
+ removedHosts.clear();
+ locations.clear();
+ portAddresses.clear();
+
log.info("Stopped");
}
@@ -153,7 +200,7 @@
descr.hwAddress(),
descr.vlan(),
new Timestamped<>(descr.location(), timestamp),
- ImmutableSet.of(descr.ipAddress()));
+ ImmutableSet.copyOf(descr.ipAddress()));
hosts.put(hostId, newhost);
locations.put(descr.location(), newhost);
return new HostEvent(HOST_ADDED, newhost);
@@ -169,12 +216,12 @@
return new HostEvent(HOST_MOVED, host);
}
- if (host.ipAddresses().contains(descr.ipAddress())) {
+ if (host.ipAddresses().containsAll(descr.ipAddress())) {
return null;
}
Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses());
- addresses.add(descr.ipAddress());
+ addresses.addAll(descr.ipAddress());
StoredHost updated = new StoredHost(providerId, host.id(),
host.mac(), host.vlan(),
host.location, addresses);
@@ -381,6 +428,10 @@
public HostLocation location() {
return location.value();
}
+
+ public Timestamp timestamp() {
+ return location.timestamp();
+ }
}
private void notifyPeers(InternalHostRemovedEvent event) throws IOException {
@@ -399,6 +450,16 @@
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 void notifyDelegateIfNotNull(HostEvent event) {
if (event != null) {
notifyDelegate(event);
@@ -434,4 +495,165 @@
notifyDelegateIfNotNull(removeHostInternal(hostId, timestamp));
}
}
+
+ private final class SendAdvertisementTask implements Runnable {
+
+ @Override
+ public void run() {
+ if (Thread.currentThread().isInterrupted()) {
+ log.info("Interrupted, quitting");
+ return;
+ }
+
+ try {
+ final NodeId self = clusterService.getLocalNode().id();
+ Set<ControllerNode> nodes = clusterService.getNodes();
+
+ ImmutableList<NodeId> nodeIds = FluentIterable.from(nodes)
+ .transform(toNodeId())
+ .toList();
+
+ if (nodeIds.size() == 1 && nodeIds.get(0).equals(self)) {
+ log.debug("No other peers in the cluster.");
+ return;
+ }
+
+ NodeId peer;
+ do {
+ int idx = RandomUtils.nextInt(0, nodeIds.size());
+ peer = nodeIds.get(idx);
+ } while (peer.equals(self));
+
+ HostAntiEntropyAdvertisement ad = createAdvertisement();
+
+ if (Thread.currentThread().isInterrupted()) {
+ log.info("Interrupted, quitting");
+ return;
+ }
+
+ try {
+ unicastMessage(peer, GossipHostStoreMessageSubjects.HOST_ANTI_ENTROPY_ADVERTISEMENT, ad);
+ } catch (IOException e) {
+ log.debug("Failed to send anti-entropy advertisement", e);
+ return;
+ }
+ } catch (Exception e) {
+ // catch all Exception to avoid Scheduled task being suppressed.
+ log.error("Exception thrown while sending advertisement", e);
+ }
+ }
+ }
+
+ private HostAntiEntropyAdvertisement createAdvertisement() {
+ final NodeId self = clusterService.getLocalNode().id();
+
+ Map<HostFragmentId, Timestamp> timestamps = new HashMap<>(hosts.size());
+ Map<HostId, Timestamp> tombstones = new HashMap<>(removedHosts.size());
+
+ for (Entry<HostId, StoredHost> e : hosts.entrySet()) {
+
+ final HostId hostId = e.getKey();
+ final StoredHost hostInfo = e.getValue();
+ final ProviderId providerId = hostInfo.providerId();
+ timestamps.put(new HostFragmentId(hostId, providerId), hostInfo.timestamp());
+ }
+
+ for (Entry<HostId, Timestamped<Host>> e : removedHosts.entrySet()) {
+ tombstones.put(e.getKey(), e.getValue().timestamp());
+ }
+
+ return new HostAntiEntropyAdvertisement(self, timestamps, tombstones);
+ }
+
+ private synchronized void handleAntiEntropyAdvertisement(HostAntiEntropyAdvertisement ad) {
+
+ final NodeId sender = ad.sender();
+
+ for (Entry<HostId, StoredHost> host : hosts.entrySet()) {
+ // for each locally live Hosts...
+ final HostId hostId = host.getKey();
+ final StoredHost localHost = host.getValue();
+ final ProviderId providerId = localHost.providerId();
+ final HostFragmentId hostFragId = new HostFragmentId(hostId, providerId);
+ final Timestamp localLiveTimestamp = localHost.timestamp();
+
+ Timestamp remoteTimestamp = ad.timestamps().get(hostFragId);
+ if (remoteTimestamp == null) {
+ remoteTimestamp = ad.tombstones().get(hostId);
+ }
+ if (remoteTimestamp == null ||
+ localLiveTimestamp.compareTo(remoteTimestamp) > 0) {
+
+ // local is more recent, push
+ // TODO: annotation is lost
+ final HostDescription desc = new DefaultHostDescription(
+ localHost.mac(),
+ localHost.vlan(),
+ localHost.location(),
+ localHost.ipAddresses());
+ try {
+ unicastMessage(sender, GossipHostStoreMessageSubjects.HOST_UPDATED,
+ new InternalHostEvent(providerId, hostId, desc, localHost.timestamp()));
+ } catch (IOException e1) {
+ log.debug("Failed to send advertisement response", e1);
+ }
+ }
+
+ final Timestamp remoteDeadTimestamp = ad.tombstones().get(hostId);
+ if (remoteDeadTimestamp != null &&
+ remoteDeadTimestamp.compareTo(localLiveTimestamp) > 0) {
+ // sender has recent remove
+ notifyDelegateIfNotNull(removeHostInternal(hostId, remoteDeadTimestamp));
+ }
+ }
+
+ for (Entry<HostId, Timestamped<Host>> dead : removedHosts.entrySet()) {
+ // for each locally dead Hosts
+ final HostId hostId = dead.getKey();
+ final Timestamp localDeadTimestamp = dead.getValue().timestamp();
+
+ // TODO: pick proper ProviderId, when supporting multi-provider
+ final ProviderId providerId = dead.getValue().value().providerId();
+ final HostFragmentId hostFragId = new HostFragmentId(hostId, providerId);
+
+ final Timestamp remoteLiveTimestamp = ad.timestamps().get(hostFragId);
+ if (remoteLiveTimestamp != null &&
+ localDeadTimestamp.compareTo(remoteLiveTimestamp) > 0) {
+ // sender has zombie, push
+ try {
+ unicastMessage(sender, GossipHostStoreMessageSubjects.HOST_REMOVED,
+ new InternalHostRemovedEvent(hostId, localDeadTimestamp));
+ } catch (IOException e1) {
+ log.debug("Failed to send advertisement response", e1);
+ }
+ }
+ }
+
+
+ for (Entry<HostId, Timestamp> e : ad.tombstones().entrySet()) {
+ // for each remote tombstone advertisement...
+ final HostId hostId = e.getKey();
+ final Timestamp adRemoveTimestamp = e.getValue();
+
+ final StoredHost storedHost = hosts.get(hostId);
+ if (storedHost == null) {
+ continue;
+ }
+ if (adRemoveTimestamp.compareTo(storedHost.timestamp()) > 0) {
+ // sender has recent remove info, locally remove
+ notifyDelegateIfNotNull(removeHostInternal(hostId, adRemoveTimestamp));
+ }
+ }
+ }
+
+ private final class InternalHostAntiEntropyAdvertisementListener implements
+ ClusterMessageHandler {
+
+ @Override
+ public void handle(ClusterMessage message) {
+ log.debug("Received Host Anti-Entropy advertisement from peer: {}", message.sender());
+ HostAntiEntropyAdvertisement advertisement = SERIALIZER.decode(message.payload());
+ handleAntiEntropyAdvertisement(advertisement);
+ }
+ }
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStoreMessageSubjects.java b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStoreMessageSubjects.java
index 27cf4ce..0a9f0e0 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStoreMessageSubjects.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStoreMessageSubjects.java
@@ -4,6 +4,11 @@
public final class GossipHostStoreMessageSubjects {
private GossipHostStoreMessageSubjects() {}
- public static final MessageSubject HOST_UPDATED = new MessageSubject("peer-host-updated");
- public static final MessageSubject HOST_REMOVED = new MessageSubject("peer-host-removed");
+
+ public static final MessageSubject HOST_UPDATED
+ = new MessageSubject("peer-host-updated");
+ public static final MessageSubject HOST_REMOVED
+ = new MessageSubject("peer-host-removed");
+ public static final MessageSubject HOST_ANTI_ENTROPY_ADVERTISEMENT
+ = new MessageSubject("host-enti-entropy-advertisement");;
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/HostAntiEntropyAdvertisement.java b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/HostAntiEntropyAdvertisement.java
new file mode 100644
index 0000000..6139005
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/HostAntiEntropyAdvertisement.java
@@ -0,0 +1,48 @@
+package org.onlab.onos.store.host.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.store.Timestamp;
+
+/**
+ * Host AE Advertisement message.
+ */
+public final class HostAntiEntropyAdvertisement {
+
+ private final NodeId sender;
+ private final Map<HostFragmentId, Timestamp> timestamps;
+ private final Map<HostId, Timestamp> tombstones;
+
+
+ public HostAntiEntropyAdvertisement(NodeId sender,
+ Map<HostFragmentId, Timestamp> timestamps,
+ Map<HostId, Timestamp> tombstones) {
+ this.sender = checkNotNull(sender);
+ this.timestamps = checkNotNull(timestamps);
+ this.tombstones = checkNotNull(tombstones);
+ }
+
+ public NodeId sender() {
+ return sender;
+ }
+
+ public Map<HostFragmentId, Timestamp> timestamps() {
+ return timestamps;
+ }
+
+ public Map<HostId, Timestamp> tombstones() {
+ return tombstones;
+ }
+
+ // For serializer
+ @SuppressWarnings("unused")
+ private HostAntiEntropyAdvertisement() {
+ this.sender = null;
+ this.timestamps = null;
+ this.tombstones = null;
+ }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/HostFragmentId.java b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/HostFragmentId.java
new file mode 100644
index 0000000..34dbff6
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/HostFragmentId.java
@@ -0,0 +1,62 @@
+package org.onlab.onos.store.host.impl;
+
+import java.util.Objects;
+
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.provider.ProviderId;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Identifier for HostDescription from a Provider.
+ */
+public final class HostFragmentId {
+ public final ProviderId providerId;
+ public final HostId hostId;
+
+ public HostFragmentId(HostId hostId, ProviderId providerId) {
+ this.providerId = providerId;
+ this.hostId = hostId;
+ }
+
+ public HostId hostId() {
+ return hostId;
+ }
+
+ public ProviderId providerId() {
+ return providerId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(providerId, hostId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof HostFragmentId)) {
+ return false;
+ }
+ HostFragmentId that = (HostFragmentId) obj;
+ return Objects.equals(this.hostId, that.hostId) &&
+ Objects.equals(this.providerId, that.providerId);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("providerId", providerId)
+ .add("hostId", hostId)
+ .toString();
+ }
+
+ // for serializer
+ @SuppressWarnings("unused")
+ private HostFragmentId() {
+ this.providerId = null;
+ this.hostId = null;
+ }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/package-info.java b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/package-info.java
new file mode 100644
index 0000000..72a4ce4
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Implementation of host store using distributed p2p synchronization protocol.
+ */
+package org.onlab.onos.store.host.impl;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/GossipLinkStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/GossipLinkStore.java
index a6c1660..6e8a367 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/GossipLinkStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/GossipLinkStore.java
@@ -1,7 +1,6 @@
package org.onlab.onos.store.link.impl;
import com.google.common.base.Function;
-import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
@@ -27,7 +26,6 @@
import org.onlab.onos.net.SparseAnnotations;
import org.onlab.onos.net.Link.Type;
import org.onlab.onos.net.LinkKey;
-import org.onlab.onos.net.Provided;
import org.onlab.onos.net.device.DeviceClockService;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.link.LinkDescription;
@@ -70,7 +68,9 @@
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
@@ -239,9 +239,9 @@
LinkKey key = linkKey(linkDescription.src(), linkDescription.dst());
final LinkEvent event;
final Timestamped<LinkDescription> mergedDesc;
- synchronized (getLinkDescriptions(key)) {
+ synchronized (getOrCreateLinkDescriptions(key)) {
event = createOrUpdateLinkInternal(providerId, deltaDesc);
- mergedDesc = getLinkDescriptions(key).get(providerId);
+ mergedDesc = getOrCreateLinkDescriptions(key).get(providerId);
}
if (event != null) {
@@ -265,7 +265,7 @@
LinkKey key = linkKey(linkDescription.value().src(),
linkDescription.value().dst());
- Map<ProviderId, Timestamped<LinkDescription>> descs = getLinkDescriptions(key);
+ Map<ProviderId, Timestamped<LinkDescription>> descs = getOrCreateLinkDescriptions(key);
synchronized (descs) {
// if the link was previously removed, we should proceed if and
@@ -296,7 +296,7 @@
ProviderId providerId,
Timestamped<LinkDescription> linkDescription) {
- // merge existing attributes and merge
+ // merge existing annotations
Timestamped<LinkDescription> existingLinkDescription = descs.get(providerId);
if (existingLinkDescription != null && existingLinkDescription.isNewer(linkDescription)) {
return null;
@@ -377,14 +377,54 @@
return event;
}
- private LinkEvent removeLinkInternal(LinkKey key, Timestamp timestamp) {
- Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions =
- getLinkDescriptions(key);
+ private static Timestamped<LinkDescription> getPrimaryDescription(
+ Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) {
+
synchronized (linkDescriptions) {
+ for (Entry<ProviderId, Timestamped<LinkDescription>>
+ e : linkDescriptions.entrySet()) {
+
+ if (!e.getKey().isAncillary()) {
+ return e.getValue();
+ }
+ }
+ }
+ return null;
+ }
+
+
+ // TODO: consider slicing out as Timestamp utils
+ /**
+ * Checks is timestamp is more recent than timestamped object.
+ *
+ * @param timestamp to check if this is more recent then other
+ * @param timestamped object to be tested against
+ * @return true if {@code timestamp} is more recent than {@code timestamped}
+ * or {@code timestamped is null}
+ */
+ private static boolean isMoreRecent(Timestamp timestamp, Timestamped<?> timestamped) {
+ checkNotNull(timestamp);
+ if (timestamped == null) {
+ return true;
+ }
+ return timestamp.compareTo(timestamped.timestamp()) > 0;
+ }
+
+ private LinkEvent removeLinkInternal(LinkKey key, Timestamp timestamp) {
+ Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions
+ = getOrCreateLinkDescriptions(key);
+
+ synchronized (linkDescriptions) {
+ if (linkDescriptions.isEmpty()) {
+ // never seen such link before. keeping timestamp for record
+ removedLinks.put(key, timestamp);
+ return null;
+ }
// accept removal request if given timestamp is newer than
// the latest Timestamp from Primary provider
- ProviderId primaryProviderId = pickPrimaryProviderId(linkDescriptions);
- if (linkDescriptions.get(primaryProviderId).isNewer(timestamp)) {
+ Timestamped<LinkDescription> prim = getPrimaryDescription(linkDescriptions);
+ if (!isMoreRecent(timestamp, prim)) {
+ // outdated remove request, ignore
return null;
}
removedLinks.put(key, timestamp);
@@ -406,12 +446,13 @@
/**
* @return primary ProviderID, or randomly chosen one if none exists
*/
- private ProviderId pickPrimaryProviderId(
+ private static ProviderId pickBaseProviderId(
Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) {
ProviderId fallBackPrimary = null;
for (Entry<ProviderId, Timestamped<LinkDescription>> e : linkDescriptions.entrySet()) {
if (!e.getKey().isAncillary()) {
+ // found primary
return e.getKey();
} else if (fallBackPrimary == null) {
// pick randomly as a fallback in case there is no primary
@@ -421,9 +462,10 @@
return fallBackPrimary;
}
+ // Guarded by linkDescs value (=locking each Link)
private Link composeLink(Map<ProviderId, Timestamped<LinkDescription>> descs) {
- ProviderId primaryProviderId = pickPrimaryProviderId(descs);
- Timestamped<LinkDescription> base = descs.get(primaryProviderId);
+ ProviderId baseProviderId = pickBaseProviderId(descs);
+ Timestamped<LinkDescription> base = descs.get(baseProviderId);
ConnectPoint src = base.value().src();
ConnectPoint dst = base.value().dst();
@@ -432,7 +474,7 @@
annotations = merge(annotations, base.value().annotations());
for (Entry<ProviderId, Timestamped<LinkDescription>> e : descs.entrySet()) {
- if (primaryProviderId.equals(e.getKey())) {
+ if (baseProviderId.equals(e.getKey())) {
continue;
}
@@ -445,10 +487,10 @@
annotations = merge(annotations, e.getValue().value().annotations());
}
- return new DefaultLink(primaryProviderId , src, dst, type, annotations);
+ return new DefaultLink(baseProviderId, src, dst, type, annotations);
}
- private Map<ProviderId, Timestamped<LinkDescription>> getLinkDescriptions(LinkKey key) {
+ private Map<ProviderId, Timestamped<LinkDescription>> getOrCreateLinkDescriptions(LinkKey key) {
Map<ProviderId, Timestamped<LinkDescription>> r;
r = linkDescs.get(key);
if (r != null) {
@@ -464,11 +506,11 @@
}
}
- private Timestamped<LinkDescription> getLinkDescription(LinkKey key, ProviderId providerId) {
- return getLinkDescriptions(key).get(providerId);
- }
-
private final Function<LinkKey, Link> lookupLink = new LookupLink();
+ /**
+ * Returns a Function to lookup Link instance using LinkKey from cache.
+ * @return
+ */
private Function<LinkKey, Link> lookupLink() {
return lookupLink;
}
@@ -476,20 +518,11 @@
private final class LookupLink implements Function<LinkKey, Link> {
@Override
public Link apply(LinkKey input) {
- return links.get(input);
- }
- }
-
- private static final class IsPrimary implements Predicate<Provided> {
-
- private static final Predicate<Provided> IS_PRIMARY = new IsPrimary();
- public static final Predicate<Provided> isPrimary() {
- return IS_PRIMARY;
- }
-
- @Override
- public boolean apply(Provided input) {
- return !input.providerId().isAncillary();
+ if (input == null) {
+ return null;
+ } else {
+ return links.get(input);
+ }
}
}
@@ -499,7 +532,6 @@
}
}
- // TODO: should we be throwing exception?
private void broadcastMessage(MessageSubject subject, Object event) throws IOException {
ClusterMessage message = new ClusterMessage(
clusterService.getLocalNode().id(),
@@ -508,17 +540,12 @@
clusterCommunicator.broadcast(message);
}
- // TODO: should we be throwing exception?
- private void unicastMessage(NodeId recipient, MessageSubject subject, Object event) {
- try {
- ClusterMessage message = new ClusterMessage(
- clusterService.getLocalNode().id(),
- subject,
- SERIALIZER.encode(event));
- clusterCommunicator.unicast(message, recipient);
- } catch (IOException e) {
- log.error("Failed to send a {} message to {}", subject.value(), recipient);
- }
+ private void unicastMessage(NodeId recipient, MessageSubject subject, Object event) throws IOException {
+ ClusterMessage message = new ClusterMessage(
+ clusterService.getLocalNode().id(),
+ subject,
+ SERIALIZER.encode(event));
+ clusterCommunicator.unicast(message, recipient);
}
private void notifyPeers(InternalLinkEvent event) throws IOException {
@@ -529,12 +556,22 @@
broadcastMessage(GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
}
+ // notify peer, silently ignoring error
private void notifyPeer(NodeId peer, InternalLinkEvent event) {
- unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_UPDATE, event);
+ try {
+ unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_UPDATE, event);
+ } catch (IOException e) {
+ log.debug("Failed to notify peer {} with message {}", peer, event);
+ }
}
+ // notify peer, silently ignoring error
private void notifyPeer(NodeId peer, InternalLinkRemovedEvent event) {
- unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
+ try {
+ unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
+ } catch (IOException e) {
+ log.debug("Failed to notify peer {} with message {}", peer, event);
+ }
}
private final class SendAdvertisementTask implements Runnable {
@@ -573,9 +610,9 @@
}
try {
- unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT, ad);
- } catch (Exception e) {
- log.error("Failed to send anti-entropy advertisement", e);
+ unicastMessage(peer, LINK_ANTI_ENTROPY_ADVERTISEMENT, ad);
+ } catch (IOException e) {
+ log.debug("Failed to send anti-entropy advertisement to {}", peer);
return;
}
} catch (Exception e) {
@@ -608,42 +645,75 @@
return new LinkAntiEntropyAdvertisement(self, linkTimestamps, linkTombstones);
}
- private void handleAntiEntropyAdvertisement(LinkAntiEntropyAdvertisement advertisement) {
+ private void handleAntiEntropyAdvertisement(LinkAntiEntropyAdvertisement ad) {
- NodeId peer = advertisement.sender();
+ final NodeId sender = ad.sender();
+ boolean localOutdated = false;
- Map<LinkFragmentId, Timestamp> linkTimestamps = advertisement.linkTimestamps();
- Map<LinkKey, Timestamp> linkTombstones = advertisement.linkTombstones();
- for (Map.Entry<LinkFragmentId, Timestamp> entry : linkTimestamps.entrySet()) {
- LinkFragmentId linkFragmentId = entry.getKey();
- Timestamp peerTimestamp = entry.getValue();
+ for (Entry<LinkKey, Map<ProviderId, Timestamped<LinkDescription>>>
+ l : linkDescs.entrySet()) {
- LinkKey key = linkFragmentId.linkKey();
- ProviderId providerId = linkFragmentId.providerId();
+ final LinkKey key = l.getKey();
+ final Map<ProviderId, Timestamped<LinkDescription>> link = l.getValue();
+ synchronized (link) {
+ Timestamp localLatest = removedLinks.get(key);
- Timestamped<LinkDescription> linkDescription = getLinkDescription(key, providerId);
- if (linkDescription.isNewer(peerTimestamp)) {
- // I have more recent link description. update peer.
- notifyPeer(peer, new InternalLinkEvent(providerId, linkDescription));
- }
- // else TODO: Peer has more recent link description. request it.
+ for (Entry<ProviderId, Timestamped<LinkDescription>> p : link.entrySet()) {
+ final ProviderId providerId = p.getKey();
+ final Timestamped<LinkDescription> pDesc = p.getValue();
- Timestamp linkRemovedTimestamp = removedLinks.get(key);
- if (linkRemovedTimestamp != null && linkRemovedTimestamp.compareTo(peerTimestamp) > 0) {
- // peer has a zombie link. update peer.
- notifyPeer(peer, new InternalLinkRemovedEvent(key, linkRemovedTimestamp));
+ final LinkFragmentId fragId = new LinkFragmentId(key, providerId);
+ // remote
+ Timestamp remoteTimestamp = ad.linkTimestamps().get(fragId);
+ if (remoteTimestamp == null) {
+ remoteTimestamp = ad.linkTombstones().get(key);
+ }
+ if (remoteTimestamp == null ||
+ pDesc.isNewer(remoteTimestamp)) {
+ // I have more recent link description. update peer.
+ notifyPeer(sender, new InternalLinkEvent(providerId, pDesc));
+ } else {
+ final Timestamp remoteLive = ad.linkTimestamps().get(fragId);
+ if (remoteLive != null &&
+ remoteLive.compareTo(pDesc.timestamp()) > 0) {
+ // I have something outdated
+ localOutdated = true;
+ }
+ }
+
+ // search local latest along the way
+ if (localLatest == null ||
+ pDesc.isNewer(localLatest)) {
+ localLatest = pDesc.timestamp();
+ }
+ }
+ // Tests if remote remove is more recent then local latest.
+ final Timestamp remoteRemove = ad.linkTombstones().get(key);
+ if (remoteRemove != null) {
+ if (localLatest != null &&
+ localLatest.compareTo(remoteRemove) < 0) {
+ // remote remove is more recent
+ notifyDelegateIfNotNull(removeLinkInternal(key, remoteRemove));
+ }
+ }
}
}
- for (Map.Entry<LinkKey, Timestamp> entry : linkTombstones.entrySet()) {
- LinkKey key = entry.getKey();
- Timestamp peerTimestamp = entry.getValue();
+ // populate remove info if not known locally
+ for (Entry<LinkKey, Timestamp> remoteRm : ad.linkTombstones().entrySet()) {
+ final LinkKey key = remoteRm.getKey();
+ final Timestamp remoteRemove = remoteRm.getValue();
+ // relying on removeLinkInternal to ignore stale info
+ notifyDelegateIfNotNull(removeLinkInternal(key, remoteRemove));
+ }
- ProviderId primaryProviderId = pickPrimaryProviderId(getLinkDescriptions(key));
- if (primaryProviderId != null) {
- if (!getLinkDescription(key, primaryProviderId).isNewer(peerTimestamp)) {
- notifyDelegateIfNotNull(removeLinkInternal(key, peerTimestamp));
- }
+ if (localOutdated) {
+ // send back advertisement to speed up convergence
+ try {
+ unicastMessage(sender, LINK_ANTI_ENTROPY_ADVERTISEMENT,
+ createAdvertisement());
+ } catch (IOException e) {
+ log.debug("Failed to send back active advertisement");
}
}
}
diff --git a/core/store/hz/net/pom.xml b/core/store/hz/net/pom.xml
deleted file mode 100644
index 177e99e..0000000
--- a/core/store/hz/net/pom.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <parent>
- <groupId>org.onlab.onos</groupId>
- <artifactId>onos-core-hz</artifactId>
- <version>1.0.0-SNAPSHOT</version>
- <relativePath>../pom.xml</relativePath>
- </parent>
-
- <artifactId>onos-core-hz-net</artifactId>
- <packaging>bundle</packaging>
-
- <description>ONOS Hazelcast based distributed store subsystems</description>
-
- <dependencies>
- <dependency>
- <groupId>org.onlab.onos</groupId>
- <artifactId>onos-api</artifactId>
- </dependency>
- <dependency>
- <groupId>org.onlab.onos</groupId>
- <artifactId>onos-core-hz-common</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.onlab.onos</groupId>
- <artifactId>onos-core-hz-common</artifactId>
- <classifier>tests</classifier>
- <scope>test</scope>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.scr.annotations</artifactId>
- </dependency>
- <dependency>
- <groupId>com.hazelcast</groupId>
- <artifactId>hazelcast</artifactId>
- </dependency>
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-scr-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
-
-</project>
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java
deleted file mode 100644
index 0016939..0000000
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java
+++ /dev/null
@@ -1,408 +0,0 @@
-package org.onlab.onos.store.device.impl;
-
-import static com.google.common.base.Predicates.notNull;
-
-import com.google.common.base.Optional;
-import com.google.common.cache.LoadingCache;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSet.Builder;
-import com.hazelcast.core.IMap;
-import com.hazelcast.core.ISet;
-
-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.DefaultDevice;
-import org.onlab.onos.net.DefaultPort;
-import org.onlab.onos.net.Device;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.Port;
-import org.onlab.onos.net.PortNumber;
-import org.onlab.onos.net.device.DeviceDescription;
-import org.onlab.onos.net.device.DeviceEvent;
-import org.onlab.onos.net.device.DeviceStore;
-import org.onlab.onos.net.device.DeviceStoreDelegate;
-import org.onlab.onos.net.device.PortDescription;
-import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.store.common.AbsentInvalidatingLoadingCache;
-import org.onlab.onos.store.common.AbstractHazelcastStore;
-import org.onlab.onos.store.common.OptionalCacheLoader;
-import org.slf4j.Logger;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.cache.CacheBuilder.newBuilder;
-import static org.onlab.onos.net.device.DeviceEvent.Type.*;
-import static org.slf4j.LoggerFactory.getLogger;
-
-//TODO: Add support for multiple provider and annotations
-/**
- * Manages inventory of infrastructure devices using Hazelcast-backed map.
- */
-@Component(immediate = true)
-@Service
-public class DistributedDeviceStore
- extends AbstractHazelcastStore<DeviceEvent, DeviceStoreDelegate>
- implements DeviceStore {
-
- private final Logger log = getLogger(getClass());
-
- public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
-
- // private IMap<DeviceId, DefaultDevice> cache;
- private IMap<byte[], byte[]> rawDevices;
- private LoadingCache<DeviceId, Optional<DefaultDevice>> devices;
-
- // private ISet<DeviceId> availableDevices;
- private ISet<byte[]> availableDevices;
-
- // TODO DevicePorts is very inefficient consider restructuring.
- // private IMap<DeviceId, Map<PortNumber, Port>> devicePorts;
- private IMap<byte[], byte[]> rawDevicePorts;
- private LoadingCache<DeviceId, Optional<Map<PortNumber, Port>>> devicePorts;
-
- private String devicesListener;
-
- private String portsListener;
-
- @Override
- @Activate
- public void activate() {
- super.activate();
-
- // IMap event handler needs value
- final boolean includeValue = true;
-
- // TODO decide on Map name scheme to avoid collision
- rawDevices = theInstance.getMap("devices");
- final OptionalCacheLoader<DeviceId, DefaultDevice> deviceLoader
- = new OptionalCacheLoader<>(serializer, rawDevices);
- devices = new AbsentInvalidatingLoadingCache<>(newBuilder().build(deviceLoader));
- // refresh/populate cache based on notification from other instance
- devicesListener = rawDevices.addEntryListener(new RemoteDeviceEventHandler(devices), includeValue);
-
- // TODO cache availableDevices
- availableDevices = theInstance.getSet("availableDevices");
-
- rawDevicePorts = theInstance.getMap("devicePorts");
- final OptionalCacheLoader<DeviceId, Map<PortNumber, Port>> devicePortLoader
- = new OptionalCacheLoader<>(serializer, rawDevicePorts);
- devicePorts = new AbsentInvalidatingLoadingCache<>(newBuilder().build(devicePortLoader));
- // refresh/populate cache based on notification from other instance
- portsListener = rawDevicePorts.addEntryListener(new RemotePortEventHandler(devicePorts), includeValue);
-
- loadDeviceCache();
- loadDevicePortsCache();
-
- log.info("Started");
- }
-
- @Deactivate
- public void deactivate() {
- rawDevicePorts.removeEntryListener(portsListener);
- rawDevices.removeEntryListener(devicesListener);
- log.info("Stopped");
- }
-
- @Override
- public int getDeviceCount() {
- return devices.asMap().size();
- }
-
- @Override
- public Iterable<Device> getDevices() {
- // TODO builder v.s. copyOf. Guava semms to be using copyOf?
- Builder<Device> builder = ImmutableSet.builder();
- for (Optional<DefaultDevice> e : devices.asMap().values()) {
- if (e.isPresent()) {
- builder.add(e.get());
- }
- }
- return builder.build();
- }
-
- private void loadDeviceCache() {
- for (byte[] keyBytes : rawDevices.keySet()) {
- final DeviceId id = deserialize(keyBytes);
- devices.refresh(id);
- }
- }
-
- private void loadDevicePortsCache() {
- for (byte[] keyBytes : rawDevicePorts.keySet()) {
- final DeviceId id = deserialize(keyBytes);
- devicePorts.refresh(id);
- }
- }
-
- @Override
- public Device getDevice(DeviceId deviceId) {
- // TODO revisit if ignoring exception is safe.
- return devices.getUnchecked(deviceId).orNull();
- }
-
- @Override
- public DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
- DeviceDescription deviceDescription) {
- DefaultDevice device = devices.getUnchecked(deviceId).orNull();
- if (device == null) {
- return createDevice(providerId, deviceId, deviceDescription);
- }
- return updateDevice(providerId, device, deviceDescription);
- }
-
- // Creates the device and returns the appropriate event if necessary.
- private DeviceEvent createDevice(ProviderId providerId, DeviceId deviceId,
- DeviceDescription desc) {
- DefaultDevice device = new DefaultDevice(providerId, deviceId, desc.type(),
- desc.manufacturer(),
- desc.hwVersion(), desc.swVersion(),
- desc.serialNumber());
-
- synchronized (this) {
- final byte[] deviceIdBytes = serialize(deviceId);
- rawDevices.put(deviceIdBytes, serialize(device));
- devices.put(deviceId, Optional.of(device));
-
- availableDevices.add(deviceIdBytes);
- }
- return new DeviceEvent(DEVICE_ADDED, device, null);
- }
-
- // Updates the device and returns the appropriate event if necessary.
- private DeviceEvent updateDevice(ProviderId providerId, DefaultDevice device,
- DeviceDescription desc) {
- // We allow only certain attributes to trigger update
- if (!Objects.equals(device.hwVersion(), desc.hwVersion()) ||
- !Objects.equals(device.swVersion(), desc.swVersion())) {
-
- DefaultDevice updated = new DefaultDevice(providerId, device.id(),
- desc.type(),
- desc.manufacturer(),
- desc.hwVersion(),
- desc.swVersion(),
- desc.serialNumber());
- synchronized (this) {
- final byte[] deviceIdBytes = serialize(device.id());
- rawDevices.put(deviceIdBytes, serialize(updated));
- devices.put(device.id(), Optional.of(updated));
- availableDevices.add(serialize(device.id()));
- }
- return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, updated, null);
- }
-
- // Otherwise merely attempt to change availability
- synchronized (this) {
- boolean added = availableDevices.add(serialize(device.id()));
- return !added ? null :
- new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
- }
- }
-
- @Override
- public DeviceEvent markOffline(DeviceId deviceId) {
- synchronized (this) {
- Device device = devices.getUnchecked(deviceId).orNull();
- boolean removed = device != null && availableDevices.remove(serialize(deviceId));
- return !removed ? null :
- new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
- }
- }
-
- @Override
- public List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
- List<PortDescription> portDescriptions) {
- List<DeviceEvent> events = new ArrayList<>();
- synchronized (this) {
- Device device = devices.getUnchecked(deviceId).orNull();
- checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
- Map<PortNumber, Port> ports = getPortMap(deviceId);
-
- // Add new ports
- Set<PortNumber> processed = new HashSet<>();
- for (PortDescription portDescription : portDescriptions) {
- Port port = ports.get(portDescription.portNumber());
- events.add(port == null ?
- createPort(device, portDescription, ports) :
- updatePort(device, port, portDescription, ports));
- processed.add(portDescription.portNumber());
- }
-
- updatePortMap(deviceId, ports);
-
- events.addAll(pruneOldPorts(device, ports, processed));
- }
- return FluentIterable.from(events).filter(notNull()).toList();
- }
-
- // Creates a new port based on the port description adds it to the map and
- // Returns corresponding event.
- //@GuardedBy("this")
- private DeviceEvent createPort(Device device, PortDescription portDescription,
- Map<PortNumber, Port> ports) {
- DefaultPort port = new DefaultPort(device, portDescription.portNumber(),
- portDescription.isEnabled());
- ports.put(port.number(), port);
- updatePortMap(device.id(), ports);
- return new DeviceEvent(PORT_ADDED, device, port);
- }
-
- // Checks if the specified port requires update and if so, it replaces the
- // existing entry in the map and returns corresponding event.
- //@GuardedBy("this")
- private DeviceEvent updatePort(Device device, Port port,
- PortDescription portDescription,
- Map<PortNumber, Port> ports) {
- if (port.isEnabled() != portDescription.isEnabled()) {
- DefaultPort updatedPort =
- new DefaultPort(device, portDescription.portNumber(),
- portDescription.isEnabled());
- ports.put(port.number(), updatedPort);
- updatePortMap(device.id(), ports);
- return new DeviceEvent(PORT_UPDATED, device, updatedPort);
- }
- return null;
- }
-
- // Prunes the specified list of ports based on which ports are in the
- // processed list and returns list of corresponding events.
- //@GuardedBy("this")
- private List<DeviceEvent> pruneOldPorts(Device device,
- Map<PortNumber, Port> ports,
- Set<PortNumber> processed) {
- List<DeviceEvent> events = new ArrayList<>();
- Iterator<PortNumber> iterator = ports.keySet().iterator();
- while (iterator.hasNext()) {
- PortNumber portNumber = iterator.next();
- if (!processed.contains(portNumber)) {
- events.add(new DeviceEvent(PORT_REMOVED, device,
- ports.get(portNumber)));
- iterator.remove();
- }
- }
- if (!events.isEmpty()) {
- updatePortMap(device.id(), ports);
- }
- return events;
- }
-
- // Gets the map of ports for the specified device; if one does not already
- // exist, it creates and registers a new one.
- // WARN: returned value is a copy, changes made to the Map
- // needs to be written back using updatePortMap
- //@GuardedBy("this")
- private Map<PortNumber, Port> getPortMap(DeviceId deviceId) {
- Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
- if (ports == null) {
- ports = new HashMap<>();
- // this probably is waste of time in most cases.
- updatePortMap(deviceId, ports);
- }
- return ports;
- }
-
- //@GuardedBy("this")
- private void updatePortMap(DeviceId deviceId, Map<PortNumber, Port> ports) {
- rawDevicePorts.put(serialize(deviceId), serialize(ports));
- devicePorts.put(deviceId, Optional.of(ports));
- }
-
- @Override
- public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
- PortDescription portDescription) {
- synchronized (this) {
- Device device = devices.getUnchecked(deviceId).orNull();
- checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
- Map<PortNumber, Port> ports = getPortMap(deviceId);
- Port port = ports.get(portDescription.portNumber());
- return updatePort(device, port, portDescription, ports);
- }
- }
-
- @Override
- public List<Port> getPorts(DeviceId deviceId) {
- Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
- return ports == null ? Collections.<Port>emptyList() : ImmutableList.copyOf(ports.values());
- }
-
- @Override
- public Port getPort(DeviceId deviceId, PortNumber portNumber) {
- Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
- return ports == null ? null : ports.get(portNumber);
- }
-
- @Override
- public boolean isAvailable(DeviceId deviceId) {
- return availableDevices.contains(serialize(deviceId));
- }
-
- @Override
- public DeviceEvent removeDevice(DeviceId deviceId) {
- synchronized (this) {
- byte[] deviceIdBytes = serialize(deviceId);
-
- // TODO conditional remove?
- Device device = deserialize(rawDevices.remove(deviceIdBytes));
- devices.invalidate(deviceId);
- return device == null ? null :
- new DeviceEvent(DEVICE_REMOVED, device, null);
- }
- }
-
- private class RemoteDeviceEventHandler extends RemoteCacheEventHandler<DeviceId, DefaultDevice> {
- public RemoteDeviceEventHandler(LoadingCache<DeviceId, Optional<DefaultDevice>> cache) {
- super(cache);
- }
-
- @Override
- protected void onAdd(DeviceId deviceId, DefaultDevice device) {
- notifyDelegate(new DeviceEvent(DEVICE_ADDED, device));
- }
-
- @Override
- protected void onRemove(DeviceId deviceId, DefaultDevice device) {
- notifyDelegate(new DeviceEvent(DEVICE_REMOVED, device));
- }
-
- @Override
- protected void onUpdate(DeviceId deviceId, DefaultDevice oldDevice, DefaultDevice device) {
- notifyDelegate(new DeviceEvent(DEVICE_UPDATED, device));
- }
- }
-
- private class RemotePortEventHandler extends RemoteCacheEventHandler<DeviceId, Map<PortNumber, Port>> {
- public RemotePortEventHandler(LoadingCache<DeviceId, Optional<Map<PortNumber, Port>>> cache) {
- super(cache);
- }
-
- @Override
- protected void onAdd(DeviceId deviceId, Map<PortNumber, Port> ports) {
-// notifyDelegate(new DeviceEvent(PORT_ADDED, getDevice(deviceId)));
- }
-
- @Override
- protected void onRemove(DeviceId deviceId, Map<PortNumber, Port> ports) {
-// notifyDelegate(new DeviceEvent(PORT_REMOVED, getDevice(deviceId)));
- }
-
- @Override
- protected void onUpdate(DeviceId deviceId, Map<PortNumber, Port> oldPorts, Map<PortNumber, Port> ports) {
-// notifyDelegate(new DeviceEvent(PORT_UPDATED, getDevice(deviceId)));
- }
- }
-
-
- // TODO cache serialized DeviceID if we suffer from serialization cost
-}
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/NoOpClockProviderService.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/NoOpClockProviderService.java
deleted file mode 100644
index 4626fa4..0000000
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/NoOpClockProviderService.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.onlab.onos.store.device.impl;
-
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Service;
-import org.onlab.onos.mastership.MastershipTerm;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.device.DeviceClockProviderService;
-
-// FIXME: Code clone in onos-core-trivial, onos-core-hz-net
-/**
- * Dummy implementation of {@link DeviceClockProviderService}.
- */
-@Component(immediate = true)
-@Service
-public class NoOpClockProviderService implements DeviceClockProviderService {
-
- @Override
- public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
- }
-}
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
deleted file mode 100644
index 084435f..0000000
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package org.onlab.onos.store.flow.impl;
-
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.Collection;
-import java.util.Collections;
-
-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.ApplicationId;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.flow.DefaultFlowEntry;
-import org.onlab.onos.net.flow.FlowEntry;
-import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
-import org.onlab.onos.net.flow.FlowRule;
-import org.onlab.onos.net.flow.FlowRuleEvent;
-import org.onlab.onos.net.flow.FlowRuleEvent.Type;
-import org.onlab.onos.net.flow.FlowRuleStore;
-import org.onlab.onos.net.flow.FlowRuleStoreDelegate;
-import org.onlab.onos.store.AbstractStore;
-import org.slf4j.Logger;
-
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
-
-/**
- * Manages inventory of flow rules using trivial in-memory implementation.
- */
-//FIXME I LIE. I AIN'T DISTRIBUTED
-@Component(immediate = true)
-@Service
-public class DistributedFlowRuleStore
- extends AbstractStore<FlowRuleEvent, FlowRuleStoreDelegate>
- implements FlowRuleStore {
-
- private final Logger log = getLogger(getClass());
-
- // store entries as a pile of rules, no info about device tables
- private final Multimap<DeviceId, FlowEntry> flowEntries =
- ArrayListMultimap.<DeviceId, FlowEntry>create();
-
- private final Multimap<Short, FlowRule> flowEntriesById =
- ArrayListMultimap.<Short, FlowRule>create();
-
- @Activate
- public void activate() {
- log.info("Started");
- }
-
- @Deactivate
- public void deactivate() {
- log.info("Stopped");
- }
-
-
- @Override
- public int getFlowRuleCount() {
- return flowEntries.size();
- }
-
- @Override
- public synchronized FlowEntry getFlowEntry(FlowRule rule) {
- for (FlowEntry f : flowEntries.get(rule.deviceId())) {
- if (f.equals(rule)) {
- return f;
- }
- }
- return null;
- }
-
- @Override
- public synchronized Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
- Collection<FlowEntry> rules = flowEntries.get(deviceId);
- if (rules == null) {
- return Collections.emptyList();
- }
- return ImmutableSet.copyOf(rules);
- }
-
- @Override
- public synchronized Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId) {
- Collection<FlowRule> rules = flowEntriesById.get(appId.id());
- if (rules == null) {
- return Collections.emptyList();
- }
- return ImmutableSet.copyOf(rules);
- }
-
- @Override
- public synchronized void storeFlowRule(FlowRule rule) {
- FlowEntry f = new DefaultFlowEntry(rule);
- DeviceId did = f.deviceId();
- if (!flowEntries.containsEntry(did, f)) {
- flowEntries.put(did, f);
- flowEntriesById.put(rule.appId(), f);
- }
- }
-
- @Override
- public synchronized void deleteFlowRule(FlowRule rule) {
- FlowEntry entry = getFlowEntry(rule);
- if (entry == null) {
- return;
- }
- entry.setState(FlowEntryState.PENDING_REMOVE);
- }
-
- @Override
- public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
- DeviceId did = rule.deviceId();
-
- // check if this new rule is an update to an existing entry
- FlowEntry stored = getFlowEntry(rule);
- if (stored != null) {
- stored.setBytes(rule.bytes());
- stored.setLife(rule.life());
- stored.setPackets(rule.packets());
- if (stored.state() == FlowEntryState.PENDING_ADD) {
- stored.setState(FlowEntryState.ADDED);
- return new FlowRuleEvent(Type.RULE_ADDED, rule);
- }
- return new FlowRuleEvent(Type.RULE_UPDATED, rule);
- }
-
- flowEntries.put(did, rule);
- return null;
- }
-
- @Override
- public synchronized FlowRuleEvent removeFlowRule(FlowEntry rule) {
- // This is where one could mark a rule as removed and still keep it in the store.
- if (flowEntries.remove(rule.deviceId(), rule)) {
- return new FlowRuleEvent(RULE_REMOVED, rule);
- } else {
- return null;
- }
- }
-}
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/DistributedHostStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/DistributedHostStore.java
deleted file mode 100644
index 0ca4ae2..0000000
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/DistributedHostStore.java
+++ /dev/null
@@ -1,302 +0,0 @@
-package org.onlab.onos.store.host.impl;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-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.Annotations;
-import org.onlab.onos.net.ConnectPoint;
-import org.onlab.onos.net.DefaultHost;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.Host;
-import org.onlab.onos.net.HostId;
-import org.onlab.onos.net.HostLocation;
-import org.onlab.onos.net.host.HostDescription;
-import org.onlab.onos.net.host.HostEvent;
-import org.onlab.onos.net.host.HostStore;
-import org.onlab.onos.net.host.HostStoreDelegate;
-import org.onlab.onos.net.host.PortAddresses;
-import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.store.AbstractStore;
-import org.onlab.packet.IpPrefix;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.VlanId;
-import org.slf4j.Logger;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static org.onlab.onos.net.host.HostEvent.Type.*;
-import static org.slf4j.LoggerFactory.getLogger;
-
-/**
- * TEMPORARY: Manages inventory of end-station hosts using distributed
- * structures implementation.
- */
-//FIXME: I LIE I AM NOT DISTRIBUTED
-@Component(immediate = true)
-@Service
-public class DistributedHostStore
- extends AbstractStore<HostEvent, HostStoreDelegate>
- implements HostStore {
-
- private final Logger log = getLogger(getClass());
-
- // Host inventory
- private final Map<HostId, StoredHost> hosts = new ConcurrentHashMap<>(2000000, 0.75f, 16);
-
- // Hosts tracked by their location
- private final Multimap<ConnectPoint, Host> locations = HashMultimap.create();
-
- private final Map<ConnectPoint, PortAddresses> portAddresses =
- new ConcurrentHashMap<>();
-
- @Activate
- public void activate() {
- log.info("Started");
- }
-
- @Deactivate
- public void deactivate() {
- log.info("Stopped");
- }
-
- @Override
- public HostEvent createOrUpdateHost(ProviderId providerId, HostId hostId,
- HostDescription hostDescription) {
- StoredHost host = hosts.get(hostId);
- if (host == null) {
- return createHost(providerId, hostId, hostDescription);
- }
- return updateHost(providerId, host, hostDescription);
- }
-
- // creates a new host and sends HOST_ADDED
- private HostEvent createHost(ProviderId providerId, HostId hostId,
- HostDescription descr) {
- StoredHost newhost = new StoredHost(providerId, hostId,
- descr.hwAddress(),
- descr.vlan(),
- descr.location(),
- ImmutableSet.of(descr.ipAddress()));
- synchronized (this) {
- hosts.put(hostId, newhost);
- locations.put(descr.location(), newhost);
- }
- return new HostEvent(HOST_ADDED, newhost);
- }
-
- // checks for type of update to host, sends appropriate event
- private HostEvent updateHost(ProviderId providerId, StoredHost host,
- HostDescription descr) {
- HostEvent event;
- if (!host.location().equals(descr.location())) {
- host.setLocation(descr.location());
- return new HostEvent(HOST_MOVED, host);
- }
-
- if (host.ipAddresses().contains(descr.ipAddress())) {
- return null;
- }
-
- Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses());
- addresses.add(descr.ipAddress());
- StoredHost updated = new StoredHost(providerId, host.id(),
- host.mac(), host.vlan(),
- descr.location(), addresses);
- event = new HostEvent(HOST_UPDATED, updated);
- synchronized (this) {
- hosts.put(host.id(), updated);
- locations.remove(host.location(), host);
- locations.put(updated.location(), updated);
- }
- return event;
- }
-
- @Override
- public HostEvent removeHost(HostId hostId) {
- synchronized (this) {
- Host host = hosts.remove(hostId);
- if (host != null) {
- locations.remove((host.location()), host);
- return new HostEvent(HOST_REMOVED, host);
- }
- return null;
- }
- }
-
- @Override
- public int getHostCount() {
- return hosts.size();
- }
-
- @Override
- public Iterable<Host> getHosts() {
- return ImmutableSet.<Host>copyOf(hosts.values());
- }
-
- @Override
- public Host getHost(HostId hostId) {
- return hosts.get(hostId);
- }
-
- @Override
- public Set<Host> getHosts(VlanId vlanId) {
- Set<Host> vlanset = new HashSet<>();
- for (Host h : hosts.values()) {
- if (h.vlan().equals(vlanId)) {
- vlanset.add(h);
- }
- }
- return vlanset;
- }
-
- @Override
- public Set<Host> getHosts(MacAddress mac) {
- Set<Host> macset = new HashSet<>();
- for (Host h : hosts.values()) {
- if (h.mac().equals(mac)) {
- macset.add(h);
- }
- }
- return macset;
- }
-
- @Override
- public Set<Host> getHosts(IpPrefix ip) {
- Set<Host> ipset = new HashSet<>();
- for (Host h : hosts.values()) {
- if (h.ipAddresses().contains(ip)) {
- ipset.add(h);
- }
- }
- return ipset;
- }
-
- @Override
- public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
- return ImmutableSet.copyOf(locations.get(connectPoint));
- }
-
- @Override
- public Set<Host> getConnectedHosts(DeviceId deviceId) {
- Set<Host> hostset = new HashSet<>();
- for (ConnectPoint p : locations.keySet()) {
- if (p.deviceId().equals(deviceId)) {
- hostset.addAll(locations.get(p));
- }
- }
- return hostset;
- }
-
- @Override
- public void updateAddressBindings(PortAddresses addresses) {
- synchronized (portAddresses) {
- PortAddresses existing = portAddresses.get(addresses.connectPoint());
- if (existing == null) {
- portAddresses.put(addresses.connectPoint(), addresses);
- } else {
- Set<IpPrefix> union = Sets.union(existing.ips(), addresses.ips())
- .immutableCopy();
-
- MacAddress newMac = (addresses.mac() == null) ? existing.mac()
- : addresses.mac();
-
- PortAddresses newAddresses =
- new PortAddresses(addresses.connectPoint(), union, newMac);
-
- portAddresses.put(newAddresses.connectPoint(), newAddresses);
- }
- }
- }
-
- @Override
- public void removeAddressBindings(PortAddresses addresses) {
- synchronized (portAddresses) {
- PortAddresses existing = portAddresses.get(addresses.connectPoint());
- if (existing != null) {
- Set<IpPrefix> difference =
- Sets.difference(existing.ips(), addresses.ips()).immutableCopy();
-
- // If they removed the existing mac, set the new mac to null.
- // Otherwise, keep the existing mac.
- MacAddress newMac = existing.mac();
- if (addresses.mac() != null && addresses.mac().equals(existing.mac())) {
- newMac = null;
- }
-
- PortAddresses newAddresses =
- new PortAddresses(addresses.connectPoint(), difference, newMac);
-
- portAddresses.put(newAddresses.connectPoint(), newAddresses);
- }
- }
- }
-
- @Override
- public void clearAddressBindings(ConnectPoint connectPoint) {
- synchronized (portAddresses) {
- portAddresses.remove(connectPoint);
- }
- }
-
- @Override
- public Set<PortAddresses> getAddressBindings() {
- synchronized (portAddresses) {
- return new HashSet<>(portAddresses.values());
- }
- }
-
- @Override
- public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
- PortAddresses addresses;
-
- synchronized (portAddresses) {
- addresses = portAddresses.get(connectPoint);
- }
-
- if (addresses == null) {
- addresses = new PortAddresses(connectPoint, null, null);
- }
-
- return addresses;
- }
-
- // Auxiliary extension to allow location to mutate.
- private class StoredHost extends DefaultHost {
- private HostLocation location;
-
- /**
- * Creates an end-station host using the supplied information.
- *
- * @param providerId provider identity
- * @param id host identifier
- * @param mac host MAC address
- * @param vlan host VLAN identifier
- * @param location host location
- * @param ips host IP addresses
- * @param annotations optional key/value annotations
- */
- public StoredHost(ProviderId providerId, HostId id,
- MacAddress mac, VlanId vlan, HostLocation location,
- Set<IpPrefix> ips, Annotations... annotations) {
- super(providerId, id, mac, vlan, location, ips, annotations);
- this.location = location;
- }
-
- void setLocation(HostLocation location) {
- this.location = location;
- }
-
- @Override
- public HostLocation location() {
- return location;
- }
- }
-}
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/link/impl/DistributedLinkStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/link/impl/DistributedLinkStore.java
deleted file mode 100644
index 90ae6fe..0000000
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/link/impl/DistributedLinkStore.java
+++ /dev/null
@@ -1,263 +0,0 @@
-package org.onlab.onos.store.link.impl;
-
-import static com.google.common.cache.CacheBuilder.newBuilder;
-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.LINK_ADDED;
-import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
-import static org.onlab.onos.net.link.LinkEvent.Type.LINK_UPDATED;
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.HashSet;
-import java.util.Set;
-
-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.ConnectPoint;
-import org.onlab.onos.net.DefaultLink;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.Link;
-import org.onlab.onos.net.LinkKey;
-import org.onlab.onos.net.link.LinkDescription;
-import org.onlab.onos.net.link.LinkEvent;
-import org.onlab.onos.net.link.LinkStore;
-import org.onlab.onos.net.link.LinkStoreDelegate;
-import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.store.common.AbsentInvalidatingLoadingCache;
-import org.onlab.onos.store.common.AbstractHazelcastStore;
-import org.onlab.onos.store.common.OptionalCacheLoader;
-import org.slf4j.Logger;
-
-import com.google.common.base.Optional;
-import com.google.common.cache.LoadingCache;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.ImmutableSet.Builder;
-import com.hazelcast.core.IMap;
-
-//TODO: Add support for multiple provider and annotations
-/**
- * Manages inventory of infrastructure links using Hazelcast-backed map.
- */
-@Component(immediate = true)
-@Service
-public class DistributedLinkStore
- extends AbstractHazelcastStore<LinkEvent, LinkStoreDelegate>
- implements LinkStore {
-
- private final Logger log = getLogger(getClass());
-
- // Link inventory
- private IMap<byte[], byte[]> rawLinks;
- private LoadingCache<LinkKey, Optional<DefaultLink>> links;
-
- // TODO synchronize?
- // Egress and ingress link sets
- private final Multimap<DeviceId, Link> srcLinks = HashMultimap.create();
- private final Multimap<DeviceId, Link> dstLinks = HashMultimap.create();
-
- private String linksListener;
-
- @Override
- @Activate
- public void activate() {
- super.activate();
-
- boolean includeValue = true;
-
- // TODO decide on Map name scheme to avoid collision
- rawLinks = theInstance.getMap("links");
- final OptionalCacheLoader<LinkKey, DefaultLink> linkLoader
- = new OptionalCacheLoader<>(serializer, rawLinks);
- links = new AbsentInvalidatingLoadingCache<>(newBuilder().build(linkLoader));
- // refresh/populate cache based on notification from other instance
- linksListener = rawLinks.addEntryListener(new RemoteLinkEventHandler(links), includeValue);
-
- loadLinkCache();
-
- log.info("Started");
- }
-
- @Deactivate
- public void deactivate() {
- rawLinks.removeEntryListener(linksListener);
- log.info("Stopped");
- }
-
- private void loadLinkCache() {
- for (byte[] keyBytes : rawLinks.keySet()) {
- final LinkKey id = deserialize(keyBytes);
- links.refresh(id);
- }
- }
-
- @Override
- public int getLinkCount() {
- return links.asMap().size();
- }
-
- @Override
- public Iterable<Link> getLinks() {
- Builder<Link> builder = ImmutableSet.builder();
- for (Optional<DefaultLink> e : links.asMap().values()) {
- if (e.isPresent()) {
- builder.add(e.get());
- }
- }
- return builder.build();
- }
-
- @Override
- public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
- return ImmutableSet.copyOf(srcLinks.get(deviceId));
- }
-
- @Override
- public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
- return ImmutableSet.copyOf(dstLinks.get(deviceId));
- }
-
- @Override
- public Link getLink(ConnectPoint src, ConnectPoint dst) {
- return links.getUnchecked(linkKey(src, dst)).orNull();
- }
-
- @Override
- public Set<Link> getEgressLinks(ConnectPoint src) {
- Set<Link> egress = new HashSet<>();
- for (Link link : srcLinks.get(src.deviceId())) {
- if (link.src().equals(src)) {
- egress.add(link);
- }
- }
- return egress;
- }
-
- @Override
- public Set<Link> getIngressLinks(ConnectPoint dst) {
- Set<Link> ingress = new HashSet<>();
- for (Link link : dstLinks.get(dst.deviceId())) {
- if (link.dst().equals(dst)) {
- ingress.add(link);
- }
- }
- return ingress;
- }
-
- @Override
- public LinkEvent createOrUpdateLink(ProviderId providerId,
- LinkDescription linkDescription) {
- LinkKey key = linkKey(linkDescription.src(), linkDescription.dst());
- Optional<DefaultLink> link = links.getUnchecked(key);
- if (!link.isPresent()) {
- return createLink(providerId, key, linkDescription);
- }
- return updateLink(providerId, link.get(), key, linkDescription);
- }
-
- // Creates and stores the link and returns the appropriate event.
- private LinkEvent createLink(ProviderId providerId, LinkKey key,
- LinkDescription linkDescription) {
- DefaultLink link = new DefaultLink(providerId, key.src(), key.dst(),
- linkDescription.type());
- synchronized (this) {
- final byte[] keyBytes = serialize(key);
- rawLinks.put(keyBytes, serialize(link));
- links.asMap().putIfAbsent(key, Optional.of(link));
-
- addNewLink(link);
- }
- return new LinkEvent(LINK_ADDED, link);
- }
-
- // update Egress and ingress link sets
- private void addNewLink(DefaultLink link) {
- synchronized (this) {
- srcLinks.put(link.src().deviceId(), link);
- dstLinks.put(link.dst().deviceId(), link);
- }
- }
-
- // Updates, if necessary the specified link and returns the appropriate event.
- private LinkEvent updateLink(ProviderId providerId, DefaultLink link,
- LinkKey key, LinkDescription linkDescription) {
- // FIXME confirm Link update condition is OK
- if (link.type() == INDIRECT && linkDescription.type() == DIRECT) {
- synchronized (this) {
-
- DefaultLink updated =
- new DefaultLink(providerId, link.src(), link.dst(),
- linkDescription.type());
- final byte[] keyBytes = serialize(key);
- rawLinks.put(keyBytes, serialize(updated));
- links.asMap().replace(key, Optional.of(link), Optional.of(updated));
-
- replaceLink(link, updated);
- return new LinkEvent(LINK_UPDATED, updated);
- }
- }
- return null;
- }
-
- // update Egress and ingress link sets
- private void replaceLink(DefaultLink link, DefaultLink updated) {
- synchronized (this) {
- srcLinks.remove(link.src().deviceId(), link);
- dstLinks.remove(link.dst().deviceId(), link);
-
- srcLinks.put(link.src().deviceId(), updated);
- dstLinks.put(link.dst().deviceId(), updated);
- }
- }
-
- @Override
- public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) {
- synchronized (this) {
- LinkKey key = linkKey(src, dst);
- byte[] keyBytes = serialize(key);
- Link link = deserialize(rawLinks.remove(keyBytes));
- links.invalidate(key);
- if (link != null) {
- removeLink(link);
- return new LinkEvent(LINK_REMOVED, link);
- }
- return null;
- }
- }
-
- // update Egress and ingress link sets
- private void removeLink(Link link) {
- synchronized (this) {
- srcLinks.remove(link.src().deviceId(), link);
- dstLinks.remove(link.dst().deviceId(), link);
- }
- }
-
- private class RemoteLinkEventHandler extends RemoteCacheEventHandler<LinkKey, DefaultLink> {
- public RemoteLinkEventHandler(LoadingCache<LinkKey, Optional<DefaultLink>> cache) {
- super(cache);
- }
-
- @Override
- protected void onAdd(LinkKey key, DefaultLink newVal) {
- addNewLink(newVal);
- notifyDelegate(new LinkEvent(LINK_ADDED, newVal));
- }
-
- @Override
- protected void onUpdate(LinkKey key, DefaultLink oldVal, DefaultLink newVal) {
- replaceLink(oldVal, newVal);
- notifyDelegate(new LinkEvent(LINK_UPDATED, newVal));
- }
-
- @Override
- protected void onRemove(LinkKey key, DefaultLink val) {
- removeLink(val);
- notifyDelegate(new LinkEvent(LINK_REMOVED, val));
- }
- }
-}
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java
deleted file mode 100644
index 5574d27..0000000
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java
+++ /dev/null
@@ -1,444 +0,0 @@
-package org.onlab.onos.store.topology.impl;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSetMultimap;
-import org.onlab.graph.DijkstraGraphSearch;
-import org.onlab.graph.GraphPathSearch;
-import org.onlab.graph.TarjanGraphSearch;
-import org.onlab.onos.net.AbstractModel;
-import org.onlab.onos.net.ConnectPoint;
-import org.onlab.onos.net.DefaultPath;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.Link;
-import org.onlab.onos.net.Path;
-import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.net.topology.ClusterId;
-import org.onlab.onos.net.topology.DefaultTopologyCluster;
-import org.onlab.onos.net.topology.DefaultTopologyVertex;
-import org.onlab.onos.net.topology.GraphDescription;
-import org.onlab.onos.net.topology.LinkWeight;
-import org.onlab.onos.net.topology.Topology;
-import org.onlab.onos.net.topology.TopologyCluster;
-import org.onlab.onos.net.topology.TopologyEdge;
-import org.onlab.onos.net.topology.TopologyGraph;
-import org.onlab.onos.net.topology.TopologyVertex;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static com.google.common.base.MoreObjects.toStringHelper;
-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.net.Link.Type.INDIRECT;
-
-/**
- * Default implementation of the topology descriptor. This carries the
- * backing topology data.
- */
-public class DefaultTopology extends AbstractModel implements Topology {
-
- private static final DijkstraGraphSearch<TopologyVertex, TopologyEdge> DIJKSTRA =
- new DijkstraGraphSearch<>();
- private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> TARJAN =
- new TarjanGraphSearch<>();
-
- private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.net");
-
- private final long time;
- private final TopologyGraph graph;
-
- private final SCCResult<TopologyVertex, TopologyEdge> clusterResults;
- private final ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>> results;
- private final ImmutableSetMultimap<PathKey, Path> paths;
-
- private final ImmutableMap<ClusterId, TopologyCluster> clusters;
- private final ImmutableSet<ConnectPoint> infrastructurePoints;
- private final ImmutableSetMultimap<ClusterId, ConnectPoint> broadcastSets;
-
- private ImmutableMap<DeviceId, TopologyCluster> clustersByDevice;
- private ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
- private ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
-
-
- /**
- * Creates a topology descriptor attributed to the specified provider.
- *
- * @param providerId identity of the provider
- * @param description data describing the new topology
- */
- DefaultTopology(ProviderId providerId, GraphDescription description) {
- super(providerId);
- this.time = description.timestamp();
-
- // Build the graph
- this.graph = new DefaultTopologyGraph(description.vertexes(),
- description.edges());
-
- this.results = searchForShortestPaths();
- this.paths = buildPaths();
-
- this.clusterResults = searchForClusters();
- this.clusters = buildTopologyClusters();
-
- buildIndexes();
-
- this.broadcastSets = buildBroadcastSets();
- this.infrastructurePoints = findInfrastructurePoints();
- }
-
- @Override
- public long time() {
- return time;
- }
-
- @Override
- public int clusterCount() {
- return clusters.size();
- }
-
- @Override
- public int deviceCount() {
- return graph.getVertexes().size();
- }
-
- @Override
- public int linkCount() {
- return graph.getEdges().size();
- }
-
- @Override
- public int pathCount() {
- return paths.size();
- }
-
- /**
- * Returns the backing topology graph.
- *
- * @return topology graph
- */
- TopologyGraph getGraph() {
- return graph;
- }
-
- /**
- * Returns the set of topology clusters.
- *
- * @return set of clusters
- */
- Set<TopologyCluster> getClusters() {
- return ImmutableSet.copyOf(clusters.values());
- }
-
- /**
- * Returns the specified topology cluster.
- *
- * @param clusterId cluster identifier
- * @return topology cluster
- */
- TopologyCluster getCluster(ClusterId clusterId) {
- return clusters.get(clusterId);
- }
-
- /**
- * Returns the topology cluster that contains the given device.
- *
- * @param deviceId device identifier
- * @return topology cluster
- */
- TopologyCluster getCluster(DeviceId deviceId) {
- return clustersByDevice.get(deviceId);
- }
-
- /**
- * Returns the set of cluster devices.
- *
- * @param cluster topology cluster
- * @return cluster devices
- */
- Set<DeviceId> getClusterDevices(TopologyCluster cluster) {
- return devicesByCluster.get(cluster);
- }
-
- /**
- * Returns the set of cluster links.
- *
- * @param cluster topology cluster
- * @return cluster links
- */
- Set<Link> getClusterLinks(TopologyCluster cluster) {
- return linksByCluster.get(cluster);
- }
-
- /**
- * Indicates whether the given point is an infrastructure link end-point.
- *
- * @param connectPoint connection point
- * @return true if infrastructure
- */
- boolean isInfrastructure(ConnectPoint connectPoint) {
- return infrastructurePoints.contains(connectPoint);
- }
-
- /**
- * Indicates whether the given point is part of a broadcast set.
- *
- * @param connectPoint connection point
- * @return true if in broadcast set
- */
- boolean isBroadcastPoint(ConnectPoint connectPoint) {
- // Any non-infrastructure, i.e. edge points are assumed to be OK.
- if (!isInfrastructure(connectPoint)) {
- return true;
- }
-
- // Find the cluster to which the device belongs.
- TopologyCluster cluster = clustersByDevice.get(connectPoint.deviceId());
- if (cluster == null) {
- throw new IllegalArgumentException("No cluster found for device " + connectPoint.deviceId());
- }
-
- // If the broadcast set is null or empty, or if the point explicitly
- // belongs to it, return true;
- Set<ConnectPoint> points = broadcastSets.get(cluster.id());
- return points == null || points.isEmpty() || points.contains(connectPoint);
- }
-
- /**
- * Returns the size of the cluster broadcast set.
- *
- * @param clusterId cluster identifier
- * @return size of the cluster broadcast set
- */
- int broadcastSetSize(ClusterId clusterId) {
- return broadcastSets.get(clusterId).size();
- }
-
- /**
- * Returns the set of pre-computed shortest paths between source and
- * destination devices.
- *
- * @param src source device
- * @param dst destination device
- * @return set of shortest paths
- */
- Set<Path> getPaths(DeviceId src, DeviceId dst) {
- return paths.get(new PathKey(src, dst));
- }
-
- /**
- * Computes on-demand the set of shortest paths between source and
- * destination devices.
- *
- * @param src source device
- * @param dst destination device
- * @return set of shortest paths
- */
- Set<Path> getPaths(DeviceId src, DeviceId dst, LinkWeight weight) {
- GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
- DIJKSTRA.search(graph, new DefaultTopologyVertex(src),
- new DefaultTopologyVertex(dst), weight);
- ImmutableSet.Builder<Path> builder = ImmutableSet.builder();
- for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
- builder.add(networkPath(path));
- }
- return builder.build();
- }
-
-
- // Searches the graph for all shortest paths and returns the search results.
- private ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>> searchForShortestPaths() {
- ImmutableMap.Builder<DeviceId, Result<TopologyVertex, TopologyEdge>> builder = ImmutableMap.builder();
-
- // Search graph paths for each source to all destinations.
- LinkWeight weight = new HopCountLinkWeight(graph.getVertexes().size());
- for (TopologyVertex src : graph.getVertexes()) {
- builder.put(src.deviceId(), DIJKSTRA.search(graph, src, null, weight));
- }
- return builder.build();
- }
-
- // Builds network paths from the graph path search results
- private ImmutableSetMultimap<PathKey, Path> buildPaths() {
- Builder<PathKey, Path> builder = ImmutableSetMultimap.builder();
- for (DeviceId deviceId : results.keySet()) {
- Result<TopologyVertex, TopologyEdge> result = results.get(deviceId);
- for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
- builder.put(new PathKey(path.src().deviceId(), path.dst().deviceId()),
- networkPath(path));
- }
- }
- return builder.build();
- }
-
- // Converts graph path to a network path with the same cost.
- private Path networkPath(org.onlab.graph.Path<TopologyVertex, TopologyEdge> path) {
- List<Link> links = new ArrayList<>();
- for (TopologyEdge edge : path.edges()) {
- links.add(edge.link());
- }
- return new DefaultPath(PID, links, path.cost());
- }
-
-
- // Searches for SCC clusters in the network topology graph using Tarjan
- // algorithm.
- private SCCResult<TopologyVertex, TopologyEdge> searchForClusters() {
- return TARJAN.search(graph, new NoIndirectLinksWeight());
- }
-
- // Builds the topology clusters and returns the id-cluster bindings.
- private ImmutableMap<ClusterId, TopologyCluster> buildTopologyClusters() {
- ImmutableMap.Builder<ClusterId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
- SCCResult<TopologyVertex, TopologyEdge> result =
- TARJAN.search(graph, new NoIndirectLinksWeight());
-
- // Extract both vertexes and edges from the results; the lists form
- // pairs along the same index.
- List<Set<TopologyVertex>> clusterVertexes = result.clusterVertexes();
- List<Set<TopologyEdge>> clusterEdges = result.clusterEdges();
-
- // Scan over the lists and create a cluster from the results.
- for (int i = 0, n = result.clusterCount(); i < n; i++) {
- Set<TopologyVertex> vertexSet = clusterVertexes.get(i);
- Set<TopologyEdge> edgeSet = clusterEdges.get(i);
-
- ClusterId cid = ClusterId.clusterId(i);
- DefaultTopologyCluster cluster =
- new DefaultTopologyCluster(cid, vertexSet.size(), edgeSet.size(),
- findRoot(vertexSet).deviceId());
- clusterBuilder.put(cid, cluster);
- }
- return clusterBuilder.build();
- }
-
- // Finds the vertex whose device id is the lexicographical minimum in the
- // specified set.
- private TopologyVertex findRoot(Set<TopologyVertex> vertexSet) {
- TopologyVertex minVertex = null;
- for (TopologyVertex vertex : vertexSet) {
- if (minVertex == null ||
- minVertex.deviceId().toString()
- .compareTo(minVertex.deviceId().toString()) < 0) {
- minVertex = vertex;
- }
- }
- return minVertex;
- }
-
- // Processes a map of broadcast sets for each cluster.
- private ImmutableSetMultimap<ClusterId, ConnectPoint> buildBroadcastSets() {
- Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap.builder();
- for (TopologyCluster cluster : clusters.values()) {
- addClusterBroadcastSet(cluster, builder);
- }
- return builder.build();
- }
-
- // Finds all broadcast points for the cluster. These are those connection
- // points which lie along the shortest paths between the cluster root and
- // all other devices within the cluster.
- private void addClusterBroadcastSet(TopologyCluster cluster,
- Builder<ClusterId, ConnectPoint> builder) {
- // Use the graph root search results to build the broadcast set.
- Result<TopologyVertex, TopologyEdge> result = results.get(cluster.root());
- for (Map.Entry<TopologyVertex, Set<TopologyEdge>> entry : result.parents().entrySet()) {
- TopologyVertex vertex = entry.getKey();
-
- // Ignore any parents that lead outside the cluster.
- if (clustersByDevice.get(vertex.deviceId()) != cluster) {
- continue;
- }
-
- // Ignore any back-link sets that are empty.
- Set<TopologyEdge> parents = entry.getValue();
- if (parents.isEmpty()) {
- continue;
- }
-
- // Use the first back-link source and destinations to add to the
- // broadcast set.
- Link link = parents.iterator().next().link();
- builder.put(cluster.id(), link.src());
- builder.put(cluster.id(), link.dst());
- }
- }
-
- // Collects and returns an set of all infrastructure link end-points.
- private ImmutableSet<ConnectPoint> findInfrastructurePoints() {
- ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
- for (TopologyEdge edge : graph.getEdges()) {
- builder.add(edge.link().src());
- builder.add(edge.link().dst());
- }
- return builder.build();
- }
-
- // Builds cluster-devices, cluster-links and device-cluster indexes.
- private void buildIndexes() {
- // Prepare the index builders
- ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
- ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder = ImmutableSetMultimap.builder();
- ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder = ImmutableSetMultimap.builder();
-
- // Now scan through all the clusters
- for (TopologyCluster cluster : clusters.values()) {
- int i = cluster.id().index();
-
- // Scan through all the cluster vertexes.
- for (TopologyVertex vertex : clusterResults.clusterVertexes().get(i)) {
- devicesBuilder.put(cluster, vertex.deviceId());
- clusterBuilder.put(vertex.deviceId(), cluster);
- }
-
- // Scan through all the cluster edges.
- for (TopologyEdge edge : clusterResults.clusterEdges().get(i)) {
- linksBuilder.put(cluster, edge.link());
- }
- }
-
- // Finalize all indexes.
- clustersByDevice = clusterBuilder.build();
- devicesByCluster = devicesBuilder.build();
- linksByCluster = linksBuilder.build();
- }
-
- // Link weight for measuring link cost as hop count with indirect links
- // being as expensive as traversing the entire graph to assume the worst.
- private static class HopCountLinkWeight implements LinkWeight {
- private final int indirectLinkCost;
-
- HopCountLinkWeight(int indirectLinkCost) {
- this.indirectLinkCost = indirectLinkCost;
- }
-
- @Override
- 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;
- }
- }
-
- // Link weight for preventing traversal over indirect links.
- private static class NoIndirectLinksWeight implements LinkWeight {
- @Override
- public double weight(TopologyEdge edge) {
- return edge.link().type() == INDIRECT ? -1 : 1;
- }
- }
-
- @Override
- public String toString() {
- return toStringHelper(this)
- .add("time", time)
- .add("clusters", clusterCount())
- .add("devices", deviceCount())
- .add("links", linkCount())
- .add("pathCount", pathCount())
- .toString();
- }
-}
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopologyGraph.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopologyGraph.java
deleted file mode 100644
index 945ba05..0000000
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopologyGraph.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.onlab.onos.store.topology.impl;
-
-import org.onlab.graph.AdjacencyListsGraph;
-import org.onlab.onos.net.topology.TopologyEdge;
-import org.onlab.onos.net.topology.TopologyGraph;
-import org.onlab.onos.net.topology.TopologyVertex;
-
-import java.util.Set;
-
-/**
- * Default implementation of an immutable topology graph based on a generic
- * implementation of adjacency lists graph.
- */
-public class DefaultTopologyGraph
- extends AdjacencyListsGraph<TopologyVertex, TopologyEdge>
- implements TopologyGraph {
-
- /**
- * Creates a topology graph comprising of the specified vertexes and edges.
- *
- * @param vertexes set of graph vertexes
- * @param edges set of graph edges
- */
- public DefaultTopologyGraph(Set<TopologyVertex> vertexes, Set<TopologyEdge> edges) {
- super(vertexes, edges);
- }
-
-}
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java
deleted file mode 100644
index 04f5fce..0000000
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package org.onlab.onos.store.topology.impl;
-
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.List;
-import java.util.Set;
-
-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.event.Event;
-import org.onlab.onos.net.ConnectPoint;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.Link;
-import org.onlab.onos.net.Path;
-import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.net.topology.ClusterId;
-import org.onlab.onos.net.topology.GraphDescription;
-import org.onlab.onos.net.topology.LinkWeight;
-import org.onlab.onos.net.topology.Topology;
-import org.onlab.onos.net.topology.TopologyCluster;
-import org.onlab.onos.net.topology.TopologyEvent;
-import org.onlab.onos.net.topology.TopologyGraph;
-import org.onlab.onos.net.topology.TopologyStore;
-import org.onlab.onos.net.topology.TopologyStoreDelegate;
-import org.onlab.onos.store.AbstractStore;
-import org.slf4j.Logger;
-
-/**
- * TEMPORARY: Manages inventory of topology snapshots using distributed
- * structures implementation.
- */
-//FIXME: I LIE I AM NOT DISTRIBUTED
-@Component(immediate = true)
-@Service
-public class DistributedTopologyStore
-extends AbstractStore<TopologyEvent, TopologyStoreDelegate>
-implements TopologyStore {
-
- private final Logger log = getLogger(getClass());
-
- private volatile DefaultTopology current;
-
- @Activate
- public void activate() {
- log.info("Started");
- }
-
- @Deactivate
- public void deactivate() {
- log.info("Stopped");
- }
- @Override
- public Topology currentTopology() {
- return current;
- }
-
- @Override
- public boolean isLatest(Topology topology) {
- // Topology is current only if it is the same as our current topology
- return topology == current;
- }
-
- @Override
- public TopologyGraph getGraph(Topology topology) {
- return defaultTopology(topology).getGraph();
- }
-
- @Override
- public Set<TopologyCluster> getClusters(Topology topology) {
- return defaultTopology(topology).getClusters();
- }
-
- @Override
- public TopologyCluster getCluster(Topology topology, ClusterId clusterId) {
- return defaultTopology(topology).getCluster(clusterId);
- }
-
- @Override
- public Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster) {
- return defaultTopology(topology).getClusterDevices(cluster);
- }
-
- @Override
- public Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster) {
- return defaultTopology(topology).getClusterLinks(cluster);
- }
-
- @Override
- public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
- return defaultTopology(topology).getPaths(src, dst);
- }
-
- @Override
- public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
- LinkWeight weight) {
- return defaultTopology(topology).getPaths(src, dst, weight);
- }
-
- @Override
- public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
- return defaultTopology(topology).isInfrastructure(connectPoint);
- }
-
- @Override
- public boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint) {
- return defaultTopology(topology).isBroadcastPoint(connectPoint);
- }
-
- @Override
- public TopologyEvent updateTopology(ProviderId providerId,
- GraphDescription graphDescription,
- List<Event> reasons) {
- // First off, make sure that what we're given is indeed newer than
- // what we already have.
- if (current != null && graphDescription.timestamp() < current.time()) {
- return null;
- }
-
- // Have the default topology construct self from the description data.
- DefaultTopology newTopology =
- new DefaultTopology(providerId, graphDescription);
-
- // Promote the new topology to current and return a ready-to-send event.
- synchronized (this) {
- current = newTopology;
- return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED,
- current, reasons);
- }
- }
-
- // Validates the specified topology and returns it as a default
- private DefaultTopology defaultTopology(Topology topology) {
- if (topology instanceof DefaultTopology) {
- return (DefaultTopology) topology;
- }
- throw new IllegalArgumentException("Topology class " + topology.getClass() +
- " not supported");
- }
-
-}
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/PathKey.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/PathKey.java
deleted file mode 100644
index 60736b9..0000000
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/PathKey.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.onlab.onos.store.topology.impl;
-
-import org.onlab.onos.net.DeviceId;
-
-import java.util.Objects;
-
-/**
- * Key for filing pre-computed paths between source and destination devices.
- */
-class PathKey {
- private final DeviceId src;
- private final DeviceId dst;
-
- /**
- * Creates a path key from the given source/dest pair.
- * @param src source device
- * @param dst destination device
- */
- PathKey(DeviceId src, DeviceId dst) {
- this.src = src;
- this.dst = dst;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(src, dst);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj instanceof PathKey) {
- final PathKey other = (PathKey) obj;
- return Objects.equals(this.src, other.src) && Objects.equals(this.dst, other.dst);
- }
- return false;
- }
-}
diff --git a/core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java b/core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java
deleted file mode 100644
index 7e2924b..0000000
--- a/core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java
+++ /dev/null
@@ -1,390 +0,0 @@
-/**
- *
- */
-package org.onlab.onos.store.device.impl;
-
-import static org.junit.Assert.*;
-import static org.onlab.onos.net.Device.Type.SWITCH;
-import static org.onlab.onos.net.DeviceId.deviceId;
-import static org.onlab.onos.net.device.DeviceEvent.Type.*;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.onlab.onos.net.Device;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.Port;
-import org.onlab.onos.net.PortNumber;
-import org.onlab.onos.net.device.DefaultDeviceDescription;
-import org.onlab.onos.net.device.DefaultPortDescription;
-import org.onlab.onos.net.device.DeviceDescription;
-import org.onlab.onos.net.device.DeviceEvent;
-import org.onlab.onos.net.device.DeviceStoreDelegate;
-import org.onlab.onos.net.device.PortDescription;
-import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.store.common.StoreManager;
-import org.onlab.onos.store.common.StoreService;
-import org.onlab.onos.store.common.TestStoreManager;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import com.hazelcast.config.Config;
-import com.hazelcast.core.Hazelcast;
-
-/**
- * Test of the Hazelcast based distributed DeviceStore implementation.
- */
-public class DistributedDeviceStoreTest {
-
- private static final ProviderId PID = new ProviderId("of", "foo");
- private static final DeviceId DID1 = deviceId("of:foo");
- private static final DeviceId DID2 = deviceId("of:bar");
- private static final String MFR = "whitebox";
- private static final String HW = "1.1.x";
- private static final String SW1 = "3.8.1";
- private static final String SW2 = "3.9.5";
- private static final String SN = "43311-12345";
-
- private static final PortNumber P1 = PortNumber.portNumber(1);
- private static final PortNumber P2 = PortNumber.portNumber(2);
- private static final PortNumber P3 = PortNumber.portNumber(3);
-
- private DistributedDeviceStore deviceStore;
-
- private StoreManager storeManager;
-
-
- @BeforeClass
- public static void setUpBeforeClass() throws Exception {
- }
-
- @AfterClass
- public static void tearDownAfterClass() throws Exception {
- }
-
-
- @Before
- public void setUp() throws Exception {
- // TODO should find a way to clean Hazelcast instance without shutdown.
- Config config = TestStoreManager.getTestConfig();
-
- storeManager = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
- storeManager.activate();
-
- deviceStore = new TestDistributedDeviceStore(storeManager);
- deviceStore.activate();
- }
-
- @After
- public void tearDown() throws Exception {
- deviceStore.deactivate();
-
- storeManager.deactivate();
- }
-
- private void putDevice(DeviceId deviceId, String swVersion) {
- DeviceDescription description =
- new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
- HW, swVersion, SN);
- deviceStore.createOrUpdateDevice(PID, deviceId, description);
- }
-
- private static void assertDevice(DeviceId id, String swVersion, Device device) {
- assertNotNull(device);
- assertEquals(id, device.id());
- assertEquals(MFR, device.manufacturer());
- assertEquals(HW, device.hwVersion());
- assertEquals(swVersion, device.swVersion());
- assertEquals(SN, device.serialNumber());
- }
-
- @Test
- public final void testGetDeviceCount() {
- assertEquals("initialy empty", 0, deviceStore.getDeviceCount());
-
- putDevice(DID1, SW1);
- putDevice(DID2, SW2);
- putDevice(DID1, SW1);
-
- assertEquals("expect 2 uniq devices", 2, deviceStore.getDeviceCount());
- }
-
- @Test
- public final void testGetDevices() {
- assertEquals("initialy empty", 0, Iterables.size(deviceStore.getDevices()));
-
- putDevice(DID1, SW1);
- putDevice(DID2, SW2);
- putDevice(DID1, SW1);
-
- assertEquals("expect 2 uniq devices",
- 2, Iterables.size(deviceStore.getDevices()));
-
- Map<DeviceId, Device> devices = new HashMap<>();
- for (Device device : deviceStore.getDevices()) {
- devices.put(device.id(), device);
- }
-
- assertDevice(DID1, SW1, devices.get(DID1));
- assertDevice(DID2, SW2, devices.get(DID2));
-
- // add case for new node?
- }
-
- @Test
- public final void testGetDevice() {
-
- putDevice(DID1, SW1);
-
- assertDevice(DID1, SW1, deviceStore.getDevice(DID1));
- assertNull("DID2 shouldn't be there", deviceStore.getDevice(DID2));
- }
-
- @Test
- public final void testCreateOrUpdateDevice() {
- DeviceDescription description =
- new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
- HW, SW1, SN);
- DeviceEvent event = deviceStore.createOrUpdateDevice(PID, DID1, description);
- assertEquals(DEVICE_ADDED, event.type());
- assertDevice(DID1, SW1, event.subject());
-
- DeviceDescription description2 =
- new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
- HW, SW2, SN);
- DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
- assertEquals(DEVICE_UPDATED, event2.type());
- assertDevice(DID1, SW2, event2.subject());
-
- assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
- }
-
- @Test
- public final void testMarkOffline() {
-
- putDevice(DID1, SW1);
- assertTrue(deviceStore.isAvailable(DID1));
-
- DeviceEvent event = deviceStore.markOffline(DID1);
- assertEquals(DEVICE_AVAILABILITY_CHANGED, event.type());
- assertDevice(DID1, SW1, event.subject());
- assertFalse(deviceStore.isAvailable(DID1));
-
- DeviceEvent event2 = deviceStore.markOffline(DID1);
- assertNull("No change, no event", event2);
-}
-
- @Test
- public final void testUpdatePorts() {
- putDevice(DID1, SW1);
- List<PortDescription> pds = Arrays.<PortDescription>asList(
- new DefaultPortDescription(P1, true),
- new DefaultPortDescription(P2, true)
- );
-
- List<DeviceEvent> events = deviceStore.updatePorts(PID, DID1, pds);
-
- Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
- for (DeviceEvent event : events) {
- assertEquals(PORT_ADDED, event.type());
- assertDevice(DID1, SW1, event.subject());
- assertTrue("PortNumber is one of expected",
- expectedPorts.remove(event.port().number()));
- assertTrue("Port is enabled", event.port().isEnabled());
- }
- assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
-
-
- List<PortDescription> pds2 = Arrays.<PortDescription>asList(
- new DefaultPortDescription(P1, false),
- new DefaultPortDescription(P2, true),
- new DefaultPortDescription(P3, true)
- );
-
- events = deviceStore.updatePorts(PID, DID1, pds2);
- assertFalse("event should be triggered", events.isEmpty());
- for (DeviceEvent event : events) {
- PortNumber num = event.port().number();
- if (P1.equals(num)) {
- assertEquals(PORT_UPDATED, event.type());
- assertDevice(DID1, SW1, event.subject());
- assertFalse("Port is disabled", event.port().isEnabled());
- } else if (P2.equals(num)) {
- fail("P2 event not expected.");
- } else if (P3.equals(num)) {
- assertEquals(PORT_ADDED, event.type());
- assertDevice(DID1, SW1, event.subject());
- assertTrue("Port is enabled", event.port().isEnabled());
- } else {
- fail("Unknown port number encountered: " + num);
- }
- }
-
- List<PortDescription> pds3 = Arrays.<PortDescription>asList(
- new DefaultPortDescription(P1, false),
- new DefaultPortDescription(P2, true)
- );
- events = deviceStore.updatePorts(PID, DID1, pds3);
- assertFalse("event should be triggered", events.isEmpty());
- for (DeviceEvent event : events) {
- PortNumber num = event.port().number();
- if (P1.equals(num)) {
- fail("P1 event not expected.");
- } else if (P2.equals(num)) {
- fail("P2 event not expected.");
- } else if (P3.equals(num)) {
- assertEquals(PORT_REMOVED, event.type());
- assertDevice(DID1, SW1, event.subject());
- assertTrue("Port was enabled", event.port().isEnabled());
- } else {
- fail("Unknown port number encountered: " + num);
- }
- }
-
- }
-
- @Test
- public final void testUpdatePortStatus() {
- putDevice(DID1, SW1);
- List<PortDescription> pds = Arrays.<PortDescription>asList(
- new DefaultPortDescription(P1, true)
- );
- deviceStore.updatePorts(PID, DID1, pds);
-
- DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
- new DefaultPortDescription(P1, false));
- assertEquals(PORT_UPDATED, event.type());
- assertDevice(DID1, SW1, event.subject());
- assertEquals(P1, event.port().number());
- assertFalse("Port is disabled", event.port().isEnabled());
- }
-
- @Test
- public final void testGetPorts() {
- putDevice(DID1, SW1);
- putDevice(DID2, SW1);
- List<PortDescription> pds = Arrays.<PortDescription>asList(
- new DefaultPortDescription(P1, true),
- new DefaultPortDescription(P2, true)
- );
- deviceStore.updatePorts(PID, DID1, pds);
-
- Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
- List<Port> ports = deviceStore.getPorts(DID1);
- for (Port port : ports) {
- assertTrue("Port is enabled", port.isEnabled());
- assertTrue("PortNumber is one of expected",
- expectedPorts.remove(port.number()));
- }
- assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
-
-
- assertTrue("DID2 has no ports", deviceStore.getPorts(DID2).isEmpty());
- }
-
- @Test
- public final void testGetPort() {
- putDevice(DID1, SW1);
- putDevice(DID2, SW1);
- List<PortDescription> pds = Arrays.<PortDescription>asList(
- new DefaultPortDescription(P1, true),
- new DefaultPortDescription(P2, false)
- );
- deviceStore.updatePorts(PID, DID1, pds);
-
- Port port1 = deviceStore.getPort(DID1, P1);
- assertEquals(P1, port1.number());
- assertTrue("Port is enabled", port1.isEnabled());
-
- Port port2 = deviceStore.getPort(DID1, P2);
- assertEquals(P2, port2.number());
- assertFalse("Port is disabled", port2.isEnabled());
-
- Port port3 = deviceStore.getPort(DID1, P3);
- assertNull("P3 not expected", port3);
- }
-
- @Test
- public final void testRemoveDevice() {
- putDevice(DID1, SW1);
- putDevice(DID2, SW1);
-
- assertEquals(2, deviceStore.getDeviceCount());
-
- DeviceEvent event = deviceStore.removeDevice(DID1);
- assertEquals(DEVICE_REMOVED, event.type());
- assertDevice(DID1, SW1, event.subject());
-
- assertEquals(1, deviceStore.getDeviceCount());
- }
-
- // TODO add test for Port events when we have them
- @Ignore("Ignore until Delegate spec. is clear.")
- @Test
- public final void testEvents() throws InterruptedException {
- final CountDownLatch addLatch = new CountDownLatch(1);
- DeviceStoreDelegate checkAdd = new DeviceStoreDelegate() {
- @Override
- public void notify(DeviceEvent event) {
- assertEquals(DEVICE_ADDED, event.type());
- assertDevice(DID1, SW1, event.subject());
- addLatch.countDown();
- }
- };
- final CountDownLatch updateLatch = new CountDownLatch(1);
- DeviceStoreDelegate checkUpdate = new DeviceStoreDelegate() {
- @Override
- public void notify(DeviceEvent event) {
- assertEquals(DEVICE_UPDATED, event.type());
- assertDevice(DID1, SW2, event.subject());
- updateLatch.countDown();
- }
- };
- final CountDownLatch removeLatch = new CountDownLatch(1);
- DeviceStoreDelegate checkRemove = new DeviceStoreDelegate() {
- @Override
- public void notify(DeviceEvent event) {
- assertEquals(DEVICE_REMOVED, event.type());
- assertDevice(DID1, SW2, event.subject());
- removeLatch.countDown();
- }
- };
-
- DeviceDescription description =
- new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
- HW, SW1, SN);
- deviceStore.setDelegate(checkAdd);
- deviceStore.createOrUpdateDevice(PID, DID1, description);
- assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
-
-
- DeviceDescription description2 =
- new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
- HW, SW2, SN);
- deviceStore.unsetDelegate(checkAdd);
- deviceStore.setDelegate(checkUpdate);
- deviceStore.createOrUpdateDevice(PID, DID1, description2);
- assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
-
- deviceStore.unsetDelegate(checkUpdate);
- deviceStore.setDelegate(checkRemove);
- deviceStore.removeDevice(DID1);
- assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
- }
-
- private class TestDistributedDeviceStore extends DistributedDeviceStore {
- public TestDistributedDeviceStore(StoreService storeService) {
- this.storeService = storeService;
- }
- }
-}
diff --git a/core/store/hz/net/src/test/java/org/onlab/onos/store/link/impl/DistributedLinkStoreTest.java b/core/store/hz/net/src/test/java/org/onlab/onos/store/link/impl/DistributedLinkStoreTest.java
deleted file mode 100644
index 7415fed..0000000
--- a/core/store/hz/net/src/test/java/org/onlab/onos/store/link/impl/DistributedLinkStoreTest.java
+++ /dev/null
@@ -1,361 +0,0 @@
-package org.onlab.onos.store.link.impl;
-
-import static org.junit.Assert.*;
-import static org.onlab.onos.net.DeviceId.deviceId;
-import static org.onlab.onos.net.Link.Type.*;
-import static org.onlab.onos.net.LinkKey.linkKey;
-import static org.onlab.onos.net.link.LinkEvent.Type.*;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.onlab.onos.net.ConnectPoint;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.Link;
-import org.onlab.onos.net.LinkKey;
-import org.onlab.onos.net.PortNumber;
-import org.onlab.onos.net.Link.Type;
-import org.onlab.onos.net.link.DefaultLinkDescription;
-import org.onlab.onos.net.link.LinkEvent;
-import org.onlab.onos.net.link.LinkStoreDelegate;
-import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.store.common.StoreManager;
-import org.onlab.onos.store.common.StoreService;
-import org.onlab.onos.store.common.TestStoreManager;
-import com.google.common.collect.Iterables;
-import com.hazelcast.config.Config;
-import com.hazelcast.core.Hazelcast;
-
-/**
- * Test of the Hazelcast based distributed LinkStore implementation.
- */
-public class DistributedLinkStoreTest {
-
- private static final ProviderId PID = new ProviderId("of", "foo");
- private static final DeviceId DID1 = deviceId("of:foo");
- private static final DeviceId DID2 = deviceId("of:bar");
-
- private static final PortNumber P1 = PortNumber.portNumber(1);
- private static final PortNumber P2 = PortNumber.portNumber(2);
- private static final PortNumber P3 = PortNumber.portNumber(3);
-
- private StoreManager storeManager;
-
- private DistributedLinkStore linkStore;
-
- @BeforeClass
- public static void setUpBeforeClass() throws Exception {
- }
-
- @AfterClass
- public static void tearDownAfterClass() throws Exception {
- }
-
- @Before
- public void setUp() throws Exception {
- // TODO should find a way to clean Hazelcast instance without shutdown.
- Config config = TestStoreManager.getTestConfig();
-
- storeManager = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
- storeManager.activate();
-
- linkStore = new TestDistributedLinkStore(storeManager);
- linkStore.activate();
- }
-
- @After
- public void tearDown() throws Exception {
- linkStore.deactivate();
- storeManager.deactivate();
- }
-
- private void putLink(DeviceId srcId, PortNumber srcNum,
- DeviceId dstId, PortNumber dstNum, Type type) {
- ConnectPoint src = new ConnectPoint(srcId, srcNum);
- ConnectPoint dst = new ConnectPoint(dstId, dstNum);
- linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type));
- }
-
- private void putLink(LinkKey key, Type type) {
- putLink(key.src().deviceId(), key.src().port(),
- key.dst().deviceId(), key.dst().port(),
- type);
- }
-
- private static void assertLink(DeviceId srcId, PortNumber srcNum,
- DeviceId dstId, PortNumber dstNum, Type type,
- Link link) {
- assertEquals(srcId, link.src().deviceId());
- assertEquals(srcNum, link.src().port());
- assertEquals(dstId, link.dst().deviceId());
- assertEquals(dstNum, link.dst().port());
- assertEquals(type, link.type());
- }
-
- private static void assertLink(LinkKey key, Type type, Link link) {
- assertLink(key.src().deviceId(), key.src().port(),
- key.dst().deviceId(), key.dst().port(),
- type, link);
- }
-
- @Test
- 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);
-
- assertEquals("expecting 2 unique link", 2, linkStore.getLinkCount());
- }
-
- @Test
- public final void testGetLinks() {
- assertEquals("initialy empty", 0,
- Iterables.size(linkStore.getLinks()));
-
- LinkKey linkId1 = linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
- LinkKey linkId2 = linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
-
- putLink(linkId1, DIRECT);
- putLink(linkId2, DIRECT);
- putLink(linkId1, DIRECT);
-
- assertEquals("expecting 2 unique link", 2,
- Iterables.size(linkStore.getLinks()));
-
- Map<LinkKey, Link> links = new HashMap<>();
- for (Link link : linkStore.getLinks()) {
- links.put(linkKey(link), link);
- }
-
- assertLink(linkId1, DIRECT, links.get(linkId1));
- assertLink(linkId2, DIRECT, links.get(linkId2));
- }
-
- @Test
- public final void testGetDeviceEgressLinks() {
- LinkKey linkId1 = linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
- LinkKey linkId2 = linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
- LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
-
- putLink(linkId1, DIRECT);
- putLink(linkId2, DIRECT);
- putLink(linkId3, DIRECT);
-
- // DID1,P1 => DID2,P2
- // DID2,P2 => DID1,P1
- // DID1,P2 => DID2,P3
-
- Set<Link> links1 = linkStore.getDeviceEgressLinks(DID1);
- assertEquals(2, links1.size());
- // check
-
- Set<Link> links2 = linkStore.getDeviceEgressLinks(DID2);
- assertEquals(1, links2.size());
- assertLink(linkId2, DIRECT, links2.iterator().next());
- }
-
- @Test
- public final void testGetDeviceIngressLinks() {
- LinkKey linkId1 = linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
- LinkKey linkId2 = linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
- LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
-
- putLink(linkId1, DIRECT);
- putLink(linkId2, DIRECT);
- putLink(linkId3, DIRECT);
-
- // DID1,P1 => DID2,P2
- // DID2,P2 => DID1,P1
- // DID1,P2 => DID2,P3
-
- Set<Link> links1 = linkStore.getDeviceIngressLinks(DID2);
- assertEquals(2, links1.size());
- // check
-
- Set<Link> links2 = linkStore.getDeviceIngressLinks(DID1);
- assertEquals(1, links2.size());
- assertLink(linkId2, DIRECT, links2.iterator().next());
- }
-
- @Test
- public final void testGetLink() {
- ConnectPoint src = new ConnectPoint(DID1, P1);
- ConnectPoint dst = new ConnectPoint(DID2, P2);
- LinkKey linkId1 = linkKey(src, dst);
-
- putLink(linkId1, DIRECT);
-
- Link link = linkStore.getLink(src, dst);
- assertLink(linkId1, DIRECT, link);
-
- assertNull("There shouldn't be reverese link",
- linkStore.getLink(dst, src));
- }
-
- @Test
- public final void testGetEgressLinks() {
- final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
- final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
- LinkKey linkId1 = linkKey(d1P1, d2P2);
- LinkKey linkId2 = linkKey(d2P2, d1P1);
- LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
-
- putLink(linkId1, DIRECT);
- putLink(linkId2, DIRECT);
- putLink(linkId3, DIRECT);
-
- // DID1,P1 => DID2,P2
- // DID2,P2 => DID1,P1
- // DID1,P2 => DID2,P3
-
- Set<Link> links1 = linkStore.getEgressLinks(d1P1);
- assertEquals(1, links1.size());
- assertLink(linkId1, DIRECT, links1.iterator().next());
-
- Set<Link> links2 = linkStore.getEgressLinks(d2P2);
- assertEquals(1, links2.size());
- assertLink(linkId2, DIRECT, links2.iterator().next());
- }
-
- @Test
- public final void testGetIngressLinks() {
- final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
- final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
- LinkKey linkId1 = linkKey(d1P1, d2P2);
- LinkKey linkId2 = linkKey(d2P2, d1P1);
- LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
-
- putLink(linkId1, DIRECT);
- putLink(linkId2, DIRECT);
- putLink(linkId3, DIRECT);
-
- // DID1,P1 => DID2,P2
- // DID2,P2 => DID1,P1
- // DID1,P2 => DID2,P3
-
- Set<Link> links1 = linkStore.getIngressLinks(d2P2);
- assertEquals(1, links1.size());
- assertLink(linkId1, DIRECT, links1.iterator().next());
-
- Set<Link> links2 = linkStore.getIngressLinks(d1P1);
- assertEquals(1, links2.size());
- assertLink(linkId2, DIRECT, links2.iterator().next());
- }
-
- @Test
- public final void testCreateOrUpdateLink() {
- ConnectPoint src = new ConnectPoint(DID1, P1);
- ConnectPoint dst = new ConnectPoint(DID2, P2);
-
- // add link
- LinkEvent event = linkStore.createOrUpdateLink(PID,
- new DefaultLinkDescription(src, dst, INDIRECT));
-
- assertLink(DID1, P1, DID2, P2, INDIRECT, event.subject());
- assertEquals(LINK_ADDED, event.type());
-
- // update link type
- LinkEvent event2 = linkStore.createOrUpdateLink(PID,
- new DefaultLinkDescription(src, dst, DIRECT));
-
- assertLink(DID1, P1, DID2, P2, DIRECT, event2.subject());
- assertEquals(LINK_UPDATED, event2.type());
-
- // no change
- LinkEvent event3 = linkStore.createOrUpdateLink(PID,
- new DefaultLinkDescription(src, dst, DIRECT));
-
- assertNull("No change event expected", event3);
- }
-
- @Test
- public final void testRemoveLink() {
- final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
- final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
- LinkKey linkId1 = linkKey(d1P1, d2P2);
- LinkKey linkId2 = linkKey(d2P2, d1P1);
-
- putLink(linkId1, DIRECT);
- putLink(linkId2, DIRECT);
-
- // DID1,P1 => DID2,P2
- // DID2,P2 => DID1,P1
- // DID1,P2 => DID2,P3
-
- LinkEvent event = linkStore.removeLink(d1P1, d2P2);
- assertEquals(LINK_REMOVED, event.type());
- LinkEvent event2 = linkStore.removeLink(d1P1, d2P2);
- assertNull(event2);
-
- assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1));
- }
-
- @Ignore("Ignore until Delegate spec. is clear.")
- @Test
- public final void testEvents() throws InterruptedException {
-
- final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
- final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
- final LinkKey linkId1 = linkKey(d1P1, d2P2);
-
- final CountDownLatch addLatch = new CountDownLatch(1);
- LinkStoreDelegate checkAdd = new LinkStoreDelegate() {
- @Override
- public void notify(LinkEvent event) {
- assertEquals(LINK_ADDED, event.type());
- assertLink(linkId1, INDIRECT, event.subject());
- addLatch.countDown();
- }
- };
- final CountDownLatch updateLatch = new CountDownLatch(1);
- LinkStoreDelegate checkUpdate = new LinkStoreDelegate() {
- @Override
- public void notify(LinkEvent event) {
- assertEquals(LINK_UPDATED, event.type());
- assertLink(linkId1, DIRECT, event.subject());
- updateLatch.countDown();
- }
- };
- final CountDownLatch removeLatch = new CountDownLatch(1);
- LinkStoreDelegate checkRemove = new LinkStoreDelegate() {
- @Override
- public void notify(LinkEvent event) {
- assertEquals(LINK_REMOVED, event.type());
- assertLink(linkId1, DIRECT, event.subject());
- removeLatch.countDown();
- }
- };
-
- linkStore.setDelegate(checkAdd);
- putLink(linkId1, INDIRECT);
- assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
-
- linkStore.unsetDelegate(checkAdd);
- linkStore.setDelegate(checkUpdate);
- putLink(linkId1, DIRECT);
- assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
-
- linkStore.unsetDelegate(checkUpdate);
- linkStore.setDelegate(checkRemove);
- linkStore.removeLink(d1P1, d2P2);
- assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
- }
-
-
- class TestDistributedLinkStore extends DistributedLinkStore {
- TestDistributedLinkStore(StoreService storeService) {
- this.storeService = storeService;
- }
- }
-}
diff --git a/core/store/hz/pom.xml b/core/store/hz/pom.xml
index d6aa1fe..70ff03f 100644
--- a/core/store/hz/pom.xml
+++ b/core/store/hz/pom.xml
@@ -17,7 +17,6 @@
<modules>
<module>common</module>
<module>cluster</module>
- <module>net</module>
</modules>
<dependencies>
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/HostLocationSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/HostLocationSerializer.java
new file mode 100644
index 0000000..2ef09ac
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/HostLocationSerializer.java
@@ -0,0 +1,40 @@
+package org.onlab.onos.store.serializers;
+
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.HostLocation;
+import org.onlab.onos.net.PortNumber;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+* Kryo Serializer for {@link HostLocation}.
+*/
+public class HostLocationSerializer extends Serializer<HostLocation> {
+
+ /**
+ * Creates {@link HostLocation} serializer instance.
+ */
+ public HostLocationSerializer() {
+ // non-null, immutable
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, HostLocation object) {
+ kryo.writeClassAndObject(output, object.deviceId());
+ kryo.writeClassAndObject(output, object.port());
+ output.writeLong(object.time());
+ }
+
+ @Override
+ public HostLocation read(Kryo kryo, Input input, Class<HostLocation> type) {
+ DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+ PortNumber portNumber = (PortNumber) kryo.readClassAndObject(input);
+ long time = input.readLong();
+ return new HostLocation(deviceId, portNumber, time);
+ }
+
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java
index b923df7..280da1e 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java
@@ -20,8 +20,7 @@
}
@Override
- public void write(Kryo kryo, Output output,
- IpAddress object) {
+ public void write(Kryo kryo, Output output, IpAddress object) {
byte[] octs = object.toOctets();
output.writeInt(octs.length);
output.writeBytes(octs);
@@ -29,11 +28,10 @@
}
@Override
- public IpAddress read(Kryo kryo, Input input,
- Class<IpAddress> type) {
- int octLen = input.readInt();
+ public IpAddress read(Kryo kryo, Input input, Class<IpAddress> type) {
+ final int octLen = input.readInt();
byte[] octs = new byte[octLen];
- input.read(octs);
+ input.readBytes(octs);
int prefLen = input.readInt();
return IpAddress.valueOf(octs, prefLen);
}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java
index 2e92692..cf82ebb 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java
@@ -34,7 +34,7 @@
Class<IpPrefix> type) {
int octLen = input.readInt();
byte[] octs = new byte[octLen];
- input.read(octs);
+ input.readBytes(octs);
int prefLen = input.readInt();
return IpPrefix.valueOf(octs, prefLen);
}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java
index 98b1ee3..38c4dfd 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java
@@ -17,21 +17,28 @@
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Element;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.HostLocation;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.LinkKey;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.device.DefaultDeviceDescription;
import org.onlab.onos.net.device.DefaultPortDescription;
+import org.onlab.onos.net.host.DefaultHostDescription;
+import org.onlab.onos.net.host.HostDescription;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.Timestamp;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
import org.onlab.util.KryoPool;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
public final class KryoPoolUtil {
@@ -41,6 +48,8 @@
public static final KryoPool MISC = KryoPool.newBuilder()
.register(IpPrefix.class, new IpPrefixSerializer())
.register(IpAddress.class, new IpAddressSerializer())
+ .register(MacAddress.class, new MacAddressSerializer())
+ .register(VlanId.class)
.build();
// TODO: Populate other classes
@@ -51,6 +60,7 @@
.register(MISC)
.register(ImmutableMap.class, new ImmutableMapSerializer())
.register(ImmutableList.class, new ImmutableListSerializer())
+ .register(ImmutableSet.class, new ImmutableSetSerializer())
.register(
//
ArrayList.class,
@@ -69,8 +79,10 @@
DefaultPortDescription.class,
Element.class,
Link.Type.class,
- Timestamp.class
-
+ Timestamp.class,
+ HostId.class,
+ HostDescription.class,
+ DefaultHostDescription.class
)
.register(URI.class, new URISerializer())
.register(NodeId.class, new NodeIdSerializer())
@@ -82,6 +94,8 @@
.register(ConnectPoint.class, new ConnectPointSerializer())
.register(DefaultLink.class, new DefaultLinkSerializer())
.register(MastershipTerm.class, new MastershipTermSerializer())
+ .register(MastershipRole.class, new MastershipRoleSerializer())
+ .register(HostLocation.class, new HostLocationSerializer())
.build();
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MacAddressSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MacAddressSerializer.java
new file mode 100644
index 0000000..954c071
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MacAddressSerializer.java
@@ -0,0 +1,32 @@
+package org.onlab.onos.store.serializers;
+
+import org.onlab.packet.MacAddress;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link MacAddress}.
+ */
+public class MacAddressSerializer extends Serializer<MacAddress> {
+
+ /**
+ * Creates {@link MacAddress} serializer instance.
+ */
+ public MacAddressSerializer() {
+ super(false, true);
+ }
+
+ @Override
+ public void write(Kryo kryo, Output output, MacAddress object) {
+ output.writeBytes(object.getAddress());
+ }
+
+ @Override
+ public MacAddress read(Kryo kryo, Input input, Class<MacAddress> type) {
+ return MacAddress.valueOf(input.readBytes(MacAddress.MAC_ADDRESS_LENGTH));
+ }
+
+}
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
index bf99227..ef80b72 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
@@ -84,7 +84,7 @@
descr.hwAddress(),
descr.vlan(),
descr.location(),
- ImmutableSet.of(descr.ipAddress()));
+ ImmutableSet.copyOf(descr.ipAddress()));
synchronized (this) {
hosts.put(hostId, newhost);
locations.put(descr.location(), newhost);
@@ -101,12 +101,12 @@
return new HostEvent(HOST_MOVED, host);
}
- if (host.ipAddresses().contains(descr.ipAddress())) {
+ if (host.ipAddresses().containsAll(descr.ipAddress())) {
return null;
}
Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses());
- addresses.add(descr.ipAddress());
+ addresses.addAll(descr.ipAddress());
StoredHost updated = new StoredHost(providerId, host.id(),
host.mac(), host.vlan(),
descr.location(), addresses);
diff --git a/features/features.xml b/features/features.xml
index 34c70bc..f0430e4 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -30,6 +30,7 @@
<bundle>mvn:org.codehaus.jackson/jackson-core-asl/1.9.13</bundle>
<bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.13</bundle>
+ <bundle>mvn:org.onlab.onos/onlab-thirdparty/1.0.0-SNAPSHOT</bundle>
</feature>
<feature name="onos-thirdparty-web" version="1.0.0"
diff --git a/pom.xml b/pom.xml
index 9b275ab..daeb33e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -107,7 +107,13 @@
</dependency>
<dependency>
- <groupId>commons-lang</groupId>
+ <groupId>com.googlecode.concurrent-trees</groupId>
+ <artifactId>concurrent-trees</artifactId>
+ <version>2.4.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
@@ -164,6 +170,12 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>4.3.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
<version>1.9.8</version>
@@ -260,6 +272,13 @@
<artifactId>onos-of-api</artifactId>
<version>${project.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onlab-thirdparty</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-of-api</artifactId>
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
index c1c56c3..9568f1f 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
@@ -161,10 +161,10 @@
switch (l3m.subtype()) {
case IP_DST:
ip = (ModIPInstruction) i;
- return factory.actions().setNwDst(IPv4Address.of(ip.ip().toRealInt()));
+ return factory.actions().setNwDst(IPv4Address.of(ip.ip().toInt()));
case IP_SRC:
ip = (ModIPInstruction) i;
- return factory.actions().setNwSrc(IPv4Address.of(ip.ip().toRealInt()));
+ return factory.actions().setNwSrc(IPv4Address.of(ip.ip().toInt()));
default:
log.warn("Unimplemented action type {}.", l3m.subtype());
break;
@@ -220,21 +220,21 @@
case IPV4_DST:
ip = (IPCriterion) c;
if (ip.ip().isMasked()) {
- Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toRealInt()),
- IPv4Address.of(ip.ip().netmask().toRealInt()));
+ Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toInt()),
+ IPv4Address.of(ip.ip().netmask().toInt()));
mBuilder.setMasked(MatchField.IPV4_DST, maskedIp);
} else {
- mBuilder.setExact(MatchField.IPV4_DST, IPv4Address.of(ip.ip().toRealInt()));
+ mBuilder.setExact(MatchField.IPV4_DST, IPv4Address.of(ip.ip().toInt()));
}
break;
case IPV4_SRC:
ip = (IPCriterion) c;
if (ip.ip().isMasked()) {
- Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toRealInt()),
- IPv4Address.of(ip.ip().netmask().toRealInt()));
+ Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toInt()),
+ IPv4Address.of(ip.ip().netmask().toInt()));
mBuilder.setMasked(MatchField.IPV4_SRC, maskedIp);
} else {
- mBuilder.setExact(MatchField.IPV4_SRC, IPv4Address.of(ip.ip().toRealInt()));
+ mBuilder.setExact(MatchField.IPV4_SRC, IPv4Address.of(ip.ip().toInt()));
}
break;
case IP_PROTO:
diff --git a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/LinkDiscovery.java b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/LinkDiscovery.java
index 0c4502b..bc45f79 100644
--- a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/LinkDiscovery.java
+++ b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/LinkDiscovery.java
@@ -339,9 +339,14 @@
final Iterator<Integer> fastIterator = this.fastPorts.iterator();
while (fastIterator.hasNext()) {
final Integer portNumber = fastIterator.next();
+ OFPortDesc port = findPort(portNumber);
+ if (port == null) {
+ // port can be null
+ // #removePort modifies `ports` outside synchronized block
+ continue;
+ }
final int probeCount = this.portProbeCount.get(portNumber)
.getAndIncrement();
- OFPortDesc port = findPort(portNumber);
if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
this.log.debug("sending fast probe to port");
diff --git a/tools/build/envDefaults b/tools/build/envDefaults
index f1e1346..9c17212 100644
--- a/tools/build/envDefaults
+++ b/tools/build/envDefaults
@@ -25,6 +25,9 @@
export ONOS_TAR=$ONOS_STAGE.tar.gz
# Defaults for ONOS testing using remote machines.
+if [ -n "${ONOS_CELL}" -a -f $ONOS_ROOT/tools/test/cells/${ONOS_CELL} ]; then
+ . $ONOS_ROOT/tools/test/cells/${ONOS_CELL}
+fi
export ONOS_INSTALL_DIR="/opt/onos" # Installation directory on remote
export OCI="${OCI:-192.168.56.101}" # ONOS Controller Instance
export ONOS_USER="sdn" # ONOS user on remote system
diff --git a/tools/dev/bash_profile b/tools/dev/bash_profile
index e14c43b..c7f57a7 100644
--- a/tools/dev/bash_profile
+++ b/tools/dev/bash_profile
@@ -6,7 +6,13 @@
export ONOS_ROOT=${ONOS_ROOT:-~/onos-next}
# Setup some environmental context for developers
-export JAVA_HOME=${JAVA_HOME:-$(/usr/libexec/java_home -v 1.7)}
+if [ -z "${JAVA_HOME}" ]; then
+ if [ -x /usr/libexec/java_home ]; then
+ export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)
+ elif [ -d /usr/lib/jvm/java-7-openjdk-amd64 ]; then
+ export JAVA_HOME="/usr/lib/jvm/java-7-openjdk-amd64"
+ fi
+fi
export MAVEN=${MAVEN:-~/Applications/apache-maven-3.2.2}
export KARAF=${KARAF:-~/Applications/apache-karaf-3.0.1}
export KARAF_LOG=$KARAF/data/log/karaf.log
@@ -15,7 +21,6 @@
export PATH="$PATH:$ONOS_ROOT/tools/dev/bin:$ONOS_ROOT/tools/test/bin"
export PATH="$PATH:$ONOS_ROOT/tools/build"
export PATH="$PATH:$MAVEN/bin:$KARAF/bin"
-export PATH="$PATH:."
# Convenience utility to warp to various ONOS source projects
# e.g. 'o api', 'o dev', 'o'
diff --git a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
index fef0cfd..a440b98 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
@@ -2,13 +2,15 @@
import java.util.Arrays;
+
+
/**
* A class representing an IPv4 address.
* <p/>
* TODO this class is a clone of IpPrefix and still needs to be modified to
* look more like an IpAddress.
*/
-public final class IpAddress {
+public final class IpAddress implements Comparable<IpAddress> {
// TODO a comparator for netmasks? E.g. for sorting by prefix match order.
@@ -121,7 +123,7 @@
int mask = DEFAULT_MASK;
if (parts.length == 2) {
- mask = Integer.valueOf(parts[1]);
+ mask = Integer.parseInt(parts[1]);
if (mask > MAX_INET_MASK) {
throw new IllegalArgumentException(
"Value of subnet mask cannot exceed "
@@ -174,14 +176,6 @@
* @return the IP address's value as an integer
*/
public int toInt() {
- int address = 0;
- for (int i = 0; i < INET_LEN; i++) {
- address |= octets[i] << ((INET_LEN - (i + 1)) * 8);
- }
- return address;
- }
-
- public int toRealInt() {
int val = 0;
for (int i = 0; i < octets.length; i++) {
val <<= 8;
@@ -191,6 +185,15 @@
}
/**
+ * Converts the IP address to a /32 IP prefix.
+ *
+ * @return the new IP prefix
+ */
+ public IpPrefix toPrefix() {
+ return IpPrefix.valueOf(octets, MAX_INET_MASK);
+ }
+
+ /**
* Helper for computing the mask value from CIDR.
*
* @return an integer bitmask
@@ -280,6 +283,13 @@
}
@Override
+ public int compareTo(IpAddress o) {
+ Long lv = ((long) this.toInt()) & 0xffffffffL;
+ Long rv = ((long) o.toInt()) & 0xffffffffL;
+ return lv.compareTo(rv);
+ }
+
+ @Override
public int hashCode() {
final int prime = 31;
int result = 1;
diff --git a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
index 84acb82..6e1ebb5 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
@@ -120,7 +120,7 @@
int mask = DEFAULT_MASK;
if (parts.length == 2) {
- mask = Integer.valueOf(parts[1]);
+ mask = Integer.parseInt(parts[1]);
if (mask > MAX_INET_MASK) {
throw new IllegalArgumentException(
"Value of subnet mask cannot exceed "
@@ -173,14 +173,6 @@
* @return the IP address's value as an integer
*/
public int toInt() {
- int address = 0;
- for (int i = 0; i < INET_LEN; i++) {
- address |= octets[i] << ((INET_LEN - (i + 1)) * 8);
- }
- return address;
- }
-
- public int toRealInt() {
int val = 0;
for (int i = 0; i < octets.length; i++) {
val <<= 8;
diff --git a/utils/pom.xml b/utils/pom.xml
index feb60e9..df7b97b 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -23,6 +23,7 @@
<module>nio</module>
<module>osgi</module>
<module>rest</module>
+ <module>thirdparty</module>
</modules>
<dependencies>
diff --git a/utils/thirdparty/pom.xml b/utils/thirdparty/pom.xml
new file mode 100644
index 0000000..59ab818
--- /dev/null
+++ b/utils/thirdparty/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onlab-utils</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onlab-thirdparty</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>ONLab third-party dependencies</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.googlecode.concurrent-trees</groupId>
+ <artifactId>concurrent-trees</artifactId>
+ <version>2.4.0</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>2.3</version>
+ <configuration>
+ <filters>
+ <filter>
+ <artifact>com.googlecode.concurrent-trees:concurrent-trees</artifact>
+ <includes>
+ <include>com/googlecode/**</include>
+ </includes>
+
+ </filter>
+ <filter>
+ <artifact>com.google.guava:guava</artifact>
+ <excludes>
+ <exclude>**</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ com.googlecode.concurrenttrees.*
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/utils/thirdparty/src/main/java/org/onlab/thirdparty/OnlabThirdparty.java b/utils/thirdparty/src/main/java/org/onlab/thirdparty/OnlabThirdparty.java
new file mode 100644
index 0000000..df7c48a
--- /dev/null
+++ b/utils/thirdparty/src/main/java/org/onlab/thirdparty/OnlabThirdparty.java
@@ -0,0 +1,11 @@
+package org.onlab.thirdparty;
+
+
+/**
+ * Empty class required to get the onlab-thirdparty module to build properly.
+ * <p/>
+ * TODO Figure out how to remove this.
+ */
+public class OnlabThirdparty {
+
+}