Move LinkDiscovery package to ONOS namespace
- Due to the design of LinkDiscoveryManager, modification by ONOS could not be easily sliced out, thus moving the whole modified LinkDiscovery features to ONOS package namespace.
- move out TopoLinksResource to core/web to avoid mixing with FL originated code bases
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/ILinkDiscovery.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/ILinkDiscovery.java
new file mode 100644
index 0000000..6113ea8
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/ILinkDiscovery.java
@@ -0,0 +1,164 @@
+package net.onrc.onos.ofcontroller.linkdiscovery;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.ser.std.ToStringSerializer;
+import org.openflow.util.HexString;
+
+public interface ILinkDiscovery {
+
+ @JsonSerialize(using=ToStringSerializer.class)
+ public enum UpdateOperation {
+ LINK_ADDED("Link Added"), // Operation Added by ONOS
+ LINK_UPDATED("Link Updated"),
+ LINK_REMOVED("Link Removed"),
+ SWITCH_UPDATED("Switch Updated"),
+ SWITCH_REMOVED("Switch Removed"),
+ PORT_UP("Port Up"),
+ PORT_DOWN("Port Down");
+
+ private String value;
+ UpdateOperation(String v) {
+ value = v;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
+
+ public class LDUpdate {
+ protected long src;
+ protected short srcPort;
+ protected long dst;
+ protected short dstPort;
+ protected SwitchType srcType;
+ protected LinkType type;
+ protected UpdateOperation operation;
+
+ public LDUpdate(long src, short srcPort,
+ long dst, short dstPort,
+ ILinkDiscovery.LinkType type,
+ UpdateOperation operation) {
+ this.src = src;
+ this.srcPort = srcPort;
+ this.dst = dst;
+ this.dstPort = dstPort;
+ this.type = type;
+ this.operation = operation;
+ }
+
+ public LDUpdate(LDUpdate old) {
+ this.src = old.src;
+ this.srcPort = old.srcPort;
+ this.dst = old.dst;
+ this.dstPort = old.dstPort;
+ this.srcType = old.srcType;
+ this.type = old.type;
+ this.operation = old.operation;
+ }
+
+ // For updtedSwitch(sw)
+ public LDUpdate(long switchId, SwitchType stype, UpdateOperation oper ){
+ this.operation = oper;
+ this.src = switchId;
+ this.srcType = stype;
+ }
+
+ // For port up or port down.
+ public LDUpdate(long sw, short port, UpdateOperation operation) {
+ this.src = sw;
+ this.srcPort = port;
+ this.operation = operation;
+ }
+
+ public long getSrc() {
+ return src;
+ }
+
+ public short getSrcPort() {
+ return srcPort;
+ }
+
+ public long getDst() {
+ return dst;
+ }
+
+ public short getDstPort() {
+ return dstPort;
+ }
+
+ public SwitchType getSrcType() {
+ return srcType;
+ }
+
+ public LinkType getType() {
+ return type;
+ }
+
+ public UpdateOperation getOperation() {
+ return operation;
+ }
+
+ public void setOperation(UpdateOperation operation) {
+ this.operation = operation;
+ }
+
+ @Override
+ public String toString() {
+ switch (operation) {
+ case LINK_ADDED:
+ case LINK_REMOVED:
+ case LINK_UPDATED:
+ return "LDUpdate [operation=" + operation +
+ ", src=" + HexString.toHexString(src)
+ + ", srcPort=" + srcPort
+ + ", dst=" + HexString.toHexString(dst)
+ + ", dstPort=" + dstPort
+ + ", type=" + type + "]";
+ case PORT_DOWN:
+ case PORT_UP:
+ return "LDUpdate [operation=" + operation +
+ ", src=" + HexString.toHexString(src)
+ + ", srcPort=" + srcPort + "]";
+ case SWITCH_REMOVED:
+ case SWITCH_UPDATED:
+ return "LDUpdate [operation=" + operation +
+ ", src=" + HexString.toHexString(src) + "]";
+ default:
+ return "LDUpdate: Unknown update.";
+ }
+ }
+ }
+
+ public enum SwitchType {
+ BASIC_SWITCH, CORE_SWITCH
+ };
+
+ public enum LinkType {
+ INVALID_LINK {
+ @Override
+ public String toString() {
+ return "invalid";
+ }
+ },
+ DIRECT_LINK{
+ @Override
+ public String toString() {
+ return "internal";
+ }
+ },
+ MULTIHOP_LINK {
+ @Override
+ public String toString() {
+ return "external";
+ }
+ },
+ TUNNEL {
+ @Override
+ public String toString() {
+ return "tunnel";
+ }
+ }
+ };
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/ILinkDiscoveryListener.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/ILinkDiscoveryListener.java
new file mode 100644
index 0000000..9ca114f
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/ILinkDiscoveryListener.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2011, Big Switch Networks, Inc.
+ * Originally created by David Erickson, Stanford University
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ **/
+
+package net.onrc.onos.ofcontroller.linkdiscovery;
+
+public interface ILinkDiscoveryListener extends ILinkDiscovery{
+
+ public void linkDiscoveryUpdate(LDUpdate update);
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/ILinkDiscoveryService.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/ILinkDiscoveryService.java
new file mode 100644
index 0000000..99438ab
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/ILinkDiscoveryService.java
@@ -0,0 +1,84 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson, Stanford University
+*
+* Licensed under the Apache License, Version 2.0 (the "License"); you may
+* not use this file except in compliance with the License. You may obtain
+* a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+* License for the specific language governing permissions and limitations
+* under the License.
+**/
+
+package net.onrc.onos.ofcontroller.linkdiscovery;
+
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+
+public interface ILinkDiscoveryService extends IFloodlightService {
+ /**
+ * Retrieves a map of all known link connections between OpenFlow switches
+ * and the associated info (valid time, port states) for the link.
+ */
+ public Map<Link, LinkInfo> getLinks();
+
+ /**
+ * Returns link type of a given link
+ * @param info
+ * @return
+ */
+ public ILinkDiscovery.LinkType getLinkType(Link lt, LinkInfo info);
+
+ /**
+ * Returns an unmodifiable map from switch id to a set of all links with it
+ * as an endpoint.
+ */
+ public Map<Long, Set<Link>> getSwitchLinks();
+
+ /**
+ * Adds a listener to listen for ILinkDiscoveryService messages
+ * @param listener The listener that wants the notifications
+ */
+ public void addListener(ILinkDiscoveryListener listener);
+
+ /**
+ * Retrieves a set of all switch ports on which lldps are suppressed.
+ */
+ public Set<NodePortTuple> getSuppressLLDPsInfo();
+
+ /**
+ * Adds a switch port to suppress lldp set
+ */
+ public void AddToSuppressLLDPs(long sw, short port);
+
+ /**
+ * Removes a switch port from suppress lldp set
+ */
+ public void RemoveFromSuppressLLDPs(long sw, short port);
+
+ /**
+ * Get the set of quarantined ports on a switch
+ */
+ public Set<Short> getQuarantinedPorts(long sw);
+
+ /**
+ * Get the status of auto port fast feature.
+ */
+ public boolean isAutoPortFastFeature();
+
+ /**
+ * Set the state for auto port fast feature.
+ * @param autoPortFastFeature
+ */
+ public void setAutoPortFastFeature(boolean autoPortFastFeature);
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/LinkInfo.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/LinkInfo.java
new file mode 100644
index 0000000..825b0a2
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/LinkInfo.java
@@ -0,0 +1,182 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.* Originally created by David Erickson, Stanford University
+** Licensed under the Apache License, Version 2.0 (the "License"); you may
+* not use this file except in compliance with the License. You may obtain
+* a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+* License for the specific language governing permissions and limitations
+* under the License.
+**/
+
+package net.onrc.onos.ofcontroller.linkdiscovery;
+
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery.LinkType;
+
+import org.openflow.protocol.OFPhysicalPort.OFPortState;
+
+public class LinkInfo {
+
+ public LinkInfo(Long firstSeenTime,
+ Long lastLldpReceivedTime,
+ Long lastBddpReceivedTime,
+ int srcPortState,
+ int dstPortState) {
+ super();
+ this.srcPortState = srcPortState;
+ this.dstPortState = dstPortState;
+ this.firstSeenTime = firstSeenTime;
+ this.lastLldpReceivedTime = lastLldpReceivedTime;
+ this.lastBddpReceivedTime = lastBddpReceivedTime;
+ }
+
+ protected Integer srcPortState;
+ protected Integer dstPortState;
+ protected Long firstSeenTime;
+ protected Long lastLldpReceivedTime; /* Standard LLLDP received time */
+ protected Long lastBddpReceivedTime; /* Modified LLDP received time */
+
+ /** The port states stored here are topology's last knowledge of
+ * the state of the port. This mostly mirrors the state
+ * maintained in the port list in IOFSwitch (i.e. the one returned
+ * from getPort), except that during a port status message the
+ * IOFSwitch port state will already have been updated with the
+ * new port state, so topology needs to keep its own copy so that
+ * it can determine if the port state has changed and therefore
+ * requires the new state to be written to storage.
+ */
+
+
+
+ public boolean linkStpBlocked() {
+ return ((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue()) ||
+ ((dstPortState & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue());
+ }
+
+ public Long getFirstSeenTime() {
+ return firstSeenTime;
+ }
+
+ public void setFirstSeenTime(Long firstSeenTime) {
+ this.firstSeenTime = firstSeenTime;
+ }
+
+ public Long getUnicastValidTime() {
+ return lastLldpReceivedTime;
+ }
+
+ public void setUnicastValidTime(Long unicastValidTime) {
+ this.lastLldpReceivedTime = unicastValidTime;
+ }
+
+ public Long getMulticastValidTime() {
+ return lastBddpReceivedTime;
+ }
+
+ public void setMulticastValidTime(Long multicastValidTime) {
+ this.lastBddpReceivedTime = multicastValidTime;
+ }
+
+ public Integer getSrcPortState() {
+ return srcPortState;
+ }
+
+ public void setSrcPortState(Integer srcPortState) {
+ this.srcPortState = srcPortState;
+ }
+
+ public Integer getDstPortState() {
+ return dstPortState;
+ }
+
+ public void setDstPortState(int dstPortState) {
+ this.dstPortState = dstPortState;
+ }
+
+ public LinkType getLinkType() {
+ if (lastLldpReceivedTime != null) {
+ return LinkType.DIRECT_LINK;
+ } else if (lastBddpReceivedTime != null) {
+ return LinkType.MULTIHOP_LINK;
+ }
+ return LinkType.INVALID_LINK;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 5557;
+ int result = 1;
+ result = prime * result + ((firstSeenTime == null) ? 0 : firstSeenTime.hashCode());
+ result = prime * result + ((lastLldpReceivedTime == null) ? 0 : lastLldpReceivedTime.hashCode());
+ result = prime * result + ((lastBddpReceivedTime == null) ? 0 : lastBddpReceivedTime.hashCode());
+ result = prime * result + ((srcPortState == null) ? 0 : srcPortState.hashCode());
+ result = prime * result + ((dstPortState == null) ? 0 : dstPortState.hashCode());
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof LinkInfo))
+ return false;
+ LinkInfo other = (LinkInfo) obj;
+
+ if (firstSeenTime == null) {
+ if (other.firstSeenTime != null)
+ return false;
+ } else if (!firstSeenTime.equals(other.firstSeenTime))
+ return false;
+
+ if (lastLldpReceivedTime == null) {
+ if (other.lastLldpReceivedTime != null)
+ return false;
+ } else if (!lastLldpReceivedTime.equals(other.lastLldpReceivedTime))
+ return false;
+
+ if (lastBddpReceivedTime == null) {
+ if (other.lastBddpReceivedTime != null)
+ return false;
+ } else if (!lastBddpReceivedTime.equals(other.lastBddpReceivedTime))
+ return false;
+
+ if (srcPortState == null) {
+ if (other.srcPortState != null)
+ return false;
+ } else if (!srcPortState.equals(other.srcPortState))
+ return false;
+
+ if (dstPortState == null) {
+ if (other.dstPortState != null)
+ return false;
+ } else if (!dstPortState.equals(other.dstPortState))
+ return false;
+
+ return true;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "LinkInfo [unicastValidTime=" + ((lastLldpReceivedTime == null) ? "null" : lastLldpReceivedTime)
+ + ", multicastValidTime=" + ((lastBddpReceivedTime == null) ? "null" : lastBddpReceivedTime)
+ + ", srcPortState=" + ((srcPortState == null) ? "null" : srcPortState)
+ + ", dstPortState=" + ((dstPortState == null) ? "null" : srcPortState)
+ + "]";
+ }
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java
new file mode 100644
index 0000000..696c63e
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java
@@ -0,0 +1,43 @@
+package net.onrc.onos.ofcontroller.linkdiscovery.internal;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+/***
+ * Topology Cluster merge/split event history related classes and members
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologyCluster {
+ // The following fields are not stored as String to save memory
+ // They should be converted to appropriate human-readable strings by
+ // the front end (e.g. in cli in Python)
+ public long dpid;
+ public long clusterIdOld; // Switch with dpid moved from cluster x to y
+ public long clusterIdNew;
+ public String reason;
+
+ @JsonProperty("Switch")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getDpid() {
+ return dpid;
+ }
+ @JsonProperty("OldClusterId")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getClusterIdOld() {
+ return clusterIdOld;
+ }
+ @JsonProperty("NewClusterId")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getClusterIdNew() {
+ return clusterIdNew;
+ }
+ @JsonProperty("Reason")
+ public String getReason() {
+ return reason;
+ }
+
+
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java
new file mode 100644
index 0000000..5d2e955
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java
@@ -0,0 +1,62 @@
+package net.onrc.onos.ofcontroller.linkdiscovery.internal;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+/***
+ * Topology link up/down event history related classes and members
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologyLink {
+ // The following fields are not stored as String to save memory
+ // They should be converted to appropriate human-readable strings by
+ // the front end (e.g. in cli in Python)
+ public long srcSwDpid;
+ public long dstSwDpid;
+ public int srcPortState;
+ public int dstPortState;
+ public int srcSwport;
+ public int dstSwport;
+ public String linkType;
+ public String reason;
+
+ @JsonProperty("Source-Switch")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getSrcSwDpid() {
+ return srcSwDpid;
+ }
+ @JsonProperty("Dest-Switch")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getDstSwDpid() {
+ return dstSwDpid;
+ }
+ @JsonProperty("SrcPortState")
+ public int getSrcPortState() {
+ return srcPortState;
+ }
+ @JsonProperty("DstPortState")
+ public int getDstPortState() {
+ return dstPortState;
+ }
+ @JsonProperty("SrcPort")
+ public int getSrcSwport() {
+ return srcSwport;
+ }
+ @JsonProperty("DstPort")
+ public int getDstSwport() {
+ return dstSwport;
+ }
+ @JsonProperty("LinkType")
+ public String getLinkType() {
+ return linkType;
+ }
+ @JsonProperty("Reason")
+ public String getReason() {
+ return reason;
+ }
+
+
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java
new file mode 100644
index 0000000..d6afd7a
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java
@@ -0,0 +1,43 @@
+package net.onrc.onos.ofcontroller.linkdiscovery.internal;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+import net.floodlightcontroller.core.web.serializers.IPv4Serializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+/***
+ * Topology Switch event history related classes and members
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologySwitch {
+ // The following fields are not stored as String to save memory
+ // They should be converted to appropriate human-readable strings by
+ // the front end (e.g. in cli in Python)
+ public long dpid;
+ public int ipv4Addr;
+ public int l4Port;
+ public String reason;
+
+ @JsonProperty("Switch")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getDpid() {
+ return dpid;
+ }
+ @JsonProperty("IpAddr")
+ @JsonSerialize(using=IPv4Serializer.class)
+ public int getIpv4Addr() {
+ return ipv4Addr;
+ }
+ @JsonProperty("Port")
+ public int getL4Port() {
+ return l4Port;
+ }
+ @JsonProperty("Reason")
+ public String getReason() {
+ return reason;
+ }
+
+
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/LinkDiscoveryManager.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/LinkDiscoveryManager.java
new file mode 100644
index 0000000..f057a29
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/internal/LinkDiscoveryManager.java
@@ -0,0 +1,2122 @@
+/**
+ * Copyright 2011, Big Switch Networks, Inc.
+ * Originally created by David Erickson, Stanford University
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ **/
+
+package net.onrc.onos.ofcontroller.linkdiscovery.internal;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+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.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.IInfoProvider;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IOFSwitchListener;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.core.internal.OFSwitchImpl;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.SingletonTask;
+import net.floodlightcontroller.packet.BSN;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.LLDP;
+import net.floodlightcontroller.packet.LLDPTLV;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IStorageSourceListener;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.OperatorPredicate;
+import net.floodlightcontroller.storage.StorageException;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.util.EventHistory;
+import net.floodlightcontroller.util.EventHistory.EvAction;
+import net.onrc.onos.ofcontroller.core.IOnosRemoteSwitch;
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery;
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscoveryListener;
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.onrc.onos.ofcontroller.linkdiscovery.LinkInfo;
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery.LinkType;
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery.SwitchType;
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery.UpdateOperation;
+import net.onrc.onos.ofcontroller.linkdiscovery.web.LinkDiscoveryWebRoutable;
+import net.onrc.onos.registry.controller.IControllerRegistryService;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFPhysicalPort.OFPortConfig;
+import org.openflow.protocol.OFPhysicalPort.OFPortState;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFPortStatus;
+import org.openflow.protocol.OFPortStatus.OFPortReason;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class sends out LLDP messages containing the sending switch's datapath
+ * id as well as the outgoing port number. Received LLrescDP messages that
+ * match a known switch cause a new LinkTuple to be created according to the
+ * invariant rules listed below. This new LinkTuple is also passed to routing
+ * if it exists to trigger updates.
+ *
+ * This class also handles removing links that are associated to switch ports
+ * that go down, and switches that are disconnected.
+ *
+ * Invariants:
+ * -portLinks and switchLinks will not contain empty Sets outside of
+ * critical sections
+ * -portLinks contains LinkTuples where one of the src or dst
+ * SwitchPortTuple matches the map key
+ * -switchLinks contains LinkTuples where one of the src or dst
+ * SwitchPortTuple's id matches the switch id
+ * -Each LinkTuple will be indexed into switchLinks for both
+ * src.id and dst.id, and portLinks for each src and dst
+ * -The updates queue is only added to from within a held write lock
+ */
+@LogMessageCategory("Network Topology")
+public class LinkDiscoveryManager
+implements IOFMessageListener, IOFSwitchListener,
+IStorageSourceListener, ILinkDiscoveryService,
+IFloodlightModule, IInfoProvider, IHAListener {
+ protected static Logger log = LoggerFactory.getLogger(LinkDiscoveryManager.class);
+
+ // Names of table/fields for links in the storage API
+ private static final String LINK_TABLE_NAME = "controller_link";
+ private static final String LINK_ID = "id";
+ private static final String LINK_SRC_SWITCH = "src_switch_id";
+ private static final String LINK_SRC_PORT = "src_port";
+ private static final String LINK_SRC_PORT_STATE = "src_port_state";
+ private static final String LINK_DST_SWITCH = "dst_switch_id";
+ private static final String LINK_DST_PORT = "dst_port";
+ private static final String LINK_DST_PORT_STATE = "dst_port_state";
+ private static final String LINK_VALID_TIME = "valid_time";
+ private static final String LINK_TYPE = "link_type";
+ private static final String SWITCH_CONFIG_TABLE_NAME = "controller_switchconfig";
+ private static final String SWITCH_CONFIG_CORE_SWITCH = "core_switch";
+
+ protected IFloodlightProviderService floodlightProvider;
+ protected IStorageSourceService storageSource;
+ protected IThreadPoolService threadPool;
+ protected IRestApiService restApi;
+ // Registry Service for ONOS
+ protected IControllerRegistryService registryService;
+
+
+ // LLDP and BDDP fields
+ private static final byte[] LLDP_STANDARD_DST_MAC_STRING =
+ HexString.fromHexString("01:80:c2:00:00:0e");
+ private static final long LINK_LOCAL_MASK = 0xfffffffffff0L;
+ private static final long LINK_LOCAL_VALUE = 0x0180c2000000L;
+
+ // BigSwitch OUI is 5C:16:C7, so 5D:16:C7 is the multicast version
+ private static final String LLDP_BSN_DST_MAC_STRING = "5d:16:c7:00:00:01";
+ //private static final String LLDP_BSN_DST_MAC_STRING = "ff:ff:ff:ff:ff:ff";
+
+
+ // Direction TLVs are used to indicate if the LLDPs were sent
+ // periodically or in response to a recieved LLDP
+ private static final byte TLV_DIRECTION_TYPE = 0x73;
+ private static final short TLV_DIRECTION_LENGTH = 1; // 1 byte
+ private static final byte TLV_DIRECTION_VALUE_FORWARD[] = {0x01};
+ private static final byte TLV_DIRECTION_VALUE_REVERSE[] = {0x02};
+ private static final LLDPTLV forwardTLV
+ = new LLDPTLV().
+ setType((byte)TLV_DIRECTION_TYPE).
+ setLength((short)TLV_DIRECTION_LENGTH).
+ setValue(TLV_DIRECTION_VALUE_FORWARD);
+
+ private static final LLDPTLV reverseTLV
+ = new LLDPTLV().
+ setType((byte)TLV_DIRECTION_TYPE).
+ setLength((short)TLV_DIRECTION_LENGTH).
+ setValue(TLV_DIRECTION_VALUE_REVERSE);
+
+ // Link discovery task details.
+ protected SingletonTask discoveryTask;
+ protected final int DISCOVERY_TASK_INTERVAL = 1;
+ protected final int LINK_TIMEOUT = 35; // original 35 secs, aggressive 5 secs
+ protected final int LLDP_TO_ALL_INTERVAL = 15 ; //original 15 seconds, aggressive 2 secs.
+ protected long lldpClock = 0;
+ // This value is intentionally kept higher than LLDP_TO_ALL_INTERVAL.
+ // If we want to identify link failures faster, we could decrease this
+ // value to a small number, say 1 or 2 sec.
+ protected final int LLDP_TO_KNOWN_INTERVAL= 20; // LLDP frequency for known links
+
+ protected LLDPTLV controllerTLV;
+ protected ReentrantReadWriteLock lock;
+ int lldpTimeCount = 0;
+
+ /**
+ * Flag to indicate if automatic port fast is enabled or not.
+ * Default is set to false -- Initialized in the init method as well.
+ */
+ boolean autoPortFastFeature = false;
+
+ /**
+ * Map of remote switches that are not connected to this controller. This
+ * is used to learn remote switches in a distributed controller ONOS.
+ */
+ protected Map<Long, IOnosRemoteSwitch> remoteSwitches;
+
+ /**
+ * Map from link to the most recent time it was verified functioning
+ */
+ protected Map<Link, LinkInfo> links;
+
+ /**
+ * Map from switch id to a set of all links with it as an endpoint
+ */
+ protected Map<Long, Set<Link>> switchLinks;
+
+ /**
+ * Map from a id:port to the set of links containing it as an endpoint
+ */
+ protected Map<NodePortTuple, Set<Link>> portLinks;
+
+ /**
+ * Set of link tuples over which multicast LLDPs are received
+ * and unicast LLDPs are not received.
+ */
+ protected Map<NodePortTuple, Set<Link>> portBroadcastDomainLinks;
+
+ protected volatile boolean shuttingDown = false;
+
+ /* topology aware components are called in the order they were added to the
+ * the array */
+ protected ArrayList<ILinkDiscoveryListener> linkDiscoveryAware;
+ protected BlockingQueue<LDUpdate> updates;
+ protected Thread updatesThread;
+
+ /**
+ * List of ports through which LLDP/BDDPs are not sent.
+ */
+ protected Set<NodePortTuple> suppressLinkDiscovery;
+
+ /** A list of ports that are quarantined for discovering links through
+ * them. Data traffic from these ports are not allowed until the ports
+ * are released from quarantine.
+ */
+ protected LinkedBlockingQueue<NodePortTuple> quarantineQueue;
+ protected LinkedBlockingQueue<NodePortTuple> maintenanceQueue;
+ /**
+ * Quarantine task
+ */
+ protected SingletonTask bddpTask;
+ protected final int BDDP_TASK_INTERVAL = 100; // 100 ms.
+ protected final int BDDP_TASK_SIZE = 5; // # of ports per iteration
+
+ /**
+ * Map of broadcast domain ports and the last time a BDDP was either
+ * sent or received on that port.
+ */
+ protected Map<NodePortTuple, Long> broadcastDomainPortTimeMap;
+
+ /**
+ * Get the LLDP sending period in seconds.
+ * @return LLDP sending period in seconds.
+ */
+ public int getLldpFrequency() {
+ return LLDP_TO_KNOWN_INTERVAL;
+ }
+
+ /**
+ * Get the LLDP timeout value in seconds
+ * @return LLDP timeout value in seconds
+ */
+ public int getLldpTimeout() {
+ return LINK_TIMEOUT;
+ }
+
+ public Map<NodePortTuple, Set<Link>> getPortLinks() {
+ return portLinks;
+ }
+
+ public Set<NodePortTuple> getSuppressLLDPsInfo() {
+ return suppressLinkDiscovery;
+ }
+
+ /**
+ * Add a switch port to the suppressed LLDP list.
+ * Remove any known links on the switch port.
+ */
+ public void AddToSuppressLLDPs(long sw, short port)
+ {
+ NodePortTuple npt = new NodePortTuple(sw, port);
+ this.suppressLinkDiscovery.add(npt);
+ deleteLinksOnPort(npt, "LLDP suppressed.");
+ }
+
+ /**
+ * Remove a switch port from the suppressed LLDP list.
+ * Discover links on that switchport.
+ */
+ public void RemoveFromSuppressLLDPs(long sw, short port)
+ {
+ NodePortTuple npt = new NodePortTuple(sw, port);
+ this.suppressLinkDiscovery.remove(npt);
+ discover(npt);
+ }
+
+ public boolean isShuttingDown() {
+ return shuttingDown;
+ }
+
+ public boolean isFastPort(long sw, short port) {
+ return false;
+ }
+
+ public ILinkDiscovery.LinkType getLinkType(Link lt, LinkInfo info) {
+ if (info.getUnicastValidTime() != null) {
+ return ILinkDiscovery.LinkType.DIRECT_LINK;
+ } else if (info.getMulticastValidTime() != null) {
+ return ILinkDiscovery.LinkType.MULTIHOP_LINK;
+ }
+ return ILinkDiscovery.LinkType.INVALID_LINK;
+ }
+
+ @LogMessageDoc(level="ERROR",
+ message="Error in link discovery updates loop",
+ explanation="An unknown error occured while dispatching " +
+ "link update notifications",
+ recommendation=LogMessageDoc.GENERIC_ACTION)
+ private void doUpdatesThread() throws InterruptedException {
+ do {
+ LDUpdate update = updates.take();
+
+ if (linkDiscoveryAware != null) {
+ if (log.isTraceEnabled()) {
+ log.trace("Dispatching link discovery update {} {} {} {} {} for {}",
+ new Object[]{update.getOperation(),
+ HexString.toHexString(update.getSrc()), update.getSrcPort(),
+ HexString.toHexString(update.getDst()), update.getDstPort(),
+ linkDiscoveryAware});
+ }
+ try {
+ for (ILinkDiscoveryListener lda : linkDiscoveryAware) { // order maintained
+ lda.linkDiscoveryUpdate(update);
+ }
+ }
+ catch (Exception e) {
+ log.error("Error in link discovery updates loop", e);
+ }
+ }
+ } while (updates.peek() != null);
+ }
+ private boolean isLinkDiscoverySuppressed(long sw, short portNumber) {
+ return this.suppressLinkDiscovery.contains(new NodePortTuple(sw, portNumber));
+ }
+
+ protected void discoverLinks() {
+
+ // timeout known links.
+ timeoutLinks();
+
+ //increment LLDP clock
+ lldpClock = (lldpClock + 1)% LLDP_TO_ALL_INTERVAL;
+
+ if (lldpClock == 0) {
+ log.debug("Sending LLDP out on all ports.");
+ discoverOnAllPorts();
+ }
+ }
+
+
+ /**
+ * Quarantine Ports.
+ */
+ protected class QuarantineWorker implements Runnable {
+ @Override
+ public void run() {
+ try {
+ processBDDPLists();
+ }
+ catch (Exception e) {
+ log.error("Error in quarantine worker thread", e);
+ } finally {
+ bddpTask.reschedule(BDDP_TASK_INTERVAL,
+ TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+
+ /**
+ * Add a switch port to the quarantine queue. Schedule the
+ * quarantine task if the quarantine queue was empty before adding
+ * this switch port.
+ * @param npt
+ */
+ protected void addToQuarantineQueue(NodePortTuple npt) {
+ if (quarantineQueue.contains(npt) == false)
+ quarantineQueue.add(npt);
+ }
+
+ /**
+ * Remove a switch port from the quarantine queue.
+ */
+ protected void removeFromQuarantineQueue(NodePortTuple npt) {
+ // Remove all occurrences of the node port tuple from the list.
+ while (quarantineQueue.remove(npt));
+ }
+
+ /**
+ * Add a switch port to maintenance queue.
+ * @param npt
+ */
+ protected void addToMaintenanceQueue(NodePortTuple npt) {
+ // TODO We are not checking if the switch port tuple is already
+ // in the maintenance list or not. This will be an issue for
+ // really large number of switch ports in the network.
+ if (maintenanceQueue.contains(npt) == false)
+ maintenanceQueue.add(npt);
+ }
+
+ /**
+ * Remove a switch port from maintenance queue.
+ * @param npt
+ */
+ protected void removeFromMaintenanceQueue(NodePortTuple npt) {
+ // Remove all occurrences of the node port tuple from the queue.
+ while (maintenanceQueue.remove(npt));
+ }
+
+ /**
+ * This method processes the quarantine list in bursts. The task is
+ * at most once per BDDP_TASK_INTERVAL.
+ * One each call, BDDP_TASK_SIZE number of switch ports are processed.
+ * Once the BDDP packets are sent out through the switch ports, the ports
+ * are removed from the quarantine list.
+ */
+
+ protected void processBDDPLists() {
+ int count = 0;
+ Set<NodePortTuple> nptList = new HashSet<NodePortTuple>();
+
+ while(count < BDDP_TASK_SIZE && quarantineQueue.peek() !=null) {
+ NodePortTuple npt;
+ npt = quarantineQueue.remove();
+ sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, false);
+ nptList.add(npt);
+ count++;
+ }
+
+ count = 0;
+ while (count < BDDP_TASK_SIZE && maintenanceQueue.peek() != null) {
+ NodePortTuple npt;
+ npt = maintenanceQueue.remove();
+ sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, false);
+ count++;
+ }
+
+ for(NodePortTuple npt:nptList) {
+ generateSwitchPortStatusUpdate(npt.getNodeId(), npt.getPortId());
+ }
+ }
+
+ public Set<Short> getQuarantinedPorts(long sw) {
+ Set<Short> qPorts = new HashSet<Short>();
+
+ Iterator<NodePortTuple> iter = quarantineQueue.iterator();
+ while (iter.hasNext()) {
+ NodePortTuple npt = iter.next();
+ if (npt.getNodeId() == sw) {
+ qPorts.add(npt.getPortId());
+ }
+ }
+ return qPorts;
+ }
+
+ private void generateSwitchPortStatusUpdate(long sw, short port) {
+ UpdateOperation operation;
+
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) return;
+
+ OFPhysicalPort ofp = iofSwitch.getPort(port);
+ if (ofp == null) return;
+
+ int srcPortState = ofp.getState();
+ boolean portUp = ((srcPortState &
+ OFPortState.OFPPS_STP_MASK.getValue()) !=
+ OFPortState.OFPPS_STP_BLOCK.getValue());
+
+ if (portUp) operation = UpdateOperation.PORT_UP;
+ else operation = UpdateOperation.PORT_DOWN;
+
+ updates.add(new LDUpdate(sw, port, operation));
+ }
+
+ /**
+ * Send LLDP on known ports
+ */
+ protected void discoverOnKnownLinkPorts() {
+ // Copy the port set.
+ Set<NodePortTuple> nptSet = new HashSet<NodePortTuple>();
+ nptSet.addAll(portLinks.keySet());
+
+ // Send LLDP from each of them.
+ for(NodePortTuple npt: nptSet) {
+ discover(npt);
+ }
+ }
+
+ protected void discover(NodePortTuple npt) {
+ discover(npt.getNodeId(), npt.getPortId());
+ }
+
+ protected void discover(long sw, short port) {
+ sendDiscoveryMessage(sw, port, true, false);
+ }
+
+ /**
+ * Learn remote switches when running as a distributed controller ONOS
+ */
+ protected IOFSwitch addRemoteSwitch(long sw, short port) {
+ IOnosRemoteSwitch remotesw = null;
+
+ // add a switch if we have not seen it before
+ remotesw = remoteSwitches.get(sw);
+
+ if (remotesw == null) {
+ remotesw = new OFSwitchImpl();
+ remotesw.setupRemoteSwitch(sw);
+ remoteSwitches.put(remotesw.getId(), remotesw);
+ log.debug("addRemoteSwitch(): added fake remote sw {}", remotesw);
+ }
+
+ // add the port if we have not seen it before
+ if (remotesw.getPort(port) == null) {
+ OFPhysicalPort remoteport = new OFPhysicalPort();
+ remoteport.setPortNumber(port);
+ remoteport.setName("fake_" + port);
+ remoteport.setConfig(0);
+ remoteport.setState(0);
+ remotesw.setPort(remoteport);
+ log.debug("addRemoteSwitch(): added fake remote port {} to sw {}", remoteport, remotesw.getId());
+ }
+
+ return remotesw;
+ }
+
+ /**
+ * Send link discovery message out of a given switch port.
+ * The discovery message may be a standard LLDP or a modified
+ * LLDP, where the dst mac address is set to :ff.
+ *
+ * TODO: The modified LLDP will updated in the future and may
+ * use a different eth-type.
+ * @param sw
+ * @param port
+ * @param isStandard indicates standard or modified LLDP
+ * @param isReverse indicates whether the LLDP was sent as a response
+ */
+ @LogMessageDoc(level="ERROR",
+ message="Failure sending LLDP out port {port} on switch {switch}",
+ explanation="An I/O error occured while sending LLDP message " +
+ "to the switch.",
+ recommendation=LogMessageDoc.CHECK_SWITCH)
+ protected void sendDiscoveryMessage(long sw, short port,
+ boolean isStandard,
+ boolean isReverse) {
+
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) {
+ return;
+ }
+
+ if (port == OFPort.OFPP_LOCAL.getValue())
+ return;
+
+ OFPhysicalPort ofpPort = iofSwitch.getPort(port);
+
+ if (ofpPort == null) {
+ if (log.isTraceEnabled()) {
+ log.trace("Null physical port. sw={}, port={}", sw, port);
+ }
+ return;
+ }
+
+ if (isLinkDiscoverySuppressed(sw, port)) {
+ /* Dont send LLDPs out of this port as suppressLLDPs set
+ *
+ */
+ return;
+ }
+
+ // For fast ports, do not send forward LLDPs or BDDPs.
+ if (!isReverse && autoPortFastFeature && isFastPort(sw, port))
+ return;
+
+ if (log.isTraceEnabled()) {
+ log.trace("Sending LLDP packet out of swich: {}, port: {}",
+ sw, port);
+ }
+
+ // using "nearest customer bridge" MAC address for broadest possible propagation
+ // through provider and TPMR bridges (see IEEE 802.1AB-2009 and 802.1Q-2011),
+ // in particular the Linux bridge which behaves mostly like a provider bridge
+ byte[] chassisId = new byte[] {4, 0, 0, 0, 0, 0, 0}; // filled in later
+ byte[] portId = new byte[] {2, 0, 0}; // filled in later
+ byte[] ttlValue = new byte[] {0, 0x78};
+ // OpenFlow OUI - 00-26-E1
+ byte[] dpidTLVValue = new byte[] {0x0, 0x26, (byte) 0xe1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ LLDPTLV dpidTLV = new LLDPTLV().setType((byte) 127).setLength((short) dpidTLVValue.length).setValue(dpidTLVValue);
+
+ byte[] dpidArray = new byte[8];
+ ByteBuffer dpidBB = ByteBuffer.wrap(dpidArray);
+ ByteBuffer portBB = ByteBuffer.wrap(portId, 1, 2);
+
+ Long dpid = sw;
+ dpidBB.putLong(dpid);
+ // set the ethernet source mac to last 6 bytes of dpid
+ System.arraycopy(dpidArray, 2, ofpPort.getHardwareAddress(), 0, 6);
+ // set the chassis id's value to last 6 bytes of dpid
+ System.arraycopy(dpidArray, 2, chassisId, 1, 6);
+ // set the optional tlv to the full dpid
+ System.arraycopy(dpidArray, 0, dpidTLVValue, 4, 8);
+
+
+ // set the portId to the outgoing port
+ portBB.putShort(port);
+ if (log.isTraceEnabled()) {
+ log.trace("Sending LLDP out of interface: {}/{}",
+ HexString.toHexString(sw), port);
+ }
+
+ LLDP lldp = new LLDP();
+ lldp.setChassisId(new LLDPTLV().setType((byte) 1).setLength((short) chassisId.length).setValue(chassisId));
+ lldp.setPortId(new LLDPTLV().setType((byte) 2).setLength((short) portId.length).setValue(portId));
+ lldp.setTtl(new LLDPTLV().setType((byte) 3).setLength((short) ttlValue.length).setValue(ttlValue));
+ lldp.getOptionalTLVList().add(dpidTLV);
+
+ // Add the controller identifier to the TLV value.
+ lldp.getOptionalTLVList().add(controllerTLV);
+ if (isReverse) {
+ lldp.getOptionalTLVList().add(reverseTLV);
+ }else {
+ lldp.getOptionalTLVList().add(forwardTLV);
+ }
+
+ Ethernet ethernet;
+ if (isStandard) {
+ ethernet = new Ethernet()
+ .setSourceMACAddress(ofpPort.getHardwareAddress())
+ .setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING)
+ .setEtherType(Ethernet.TYPE_LLDP);
+ ethernet.setPayload(lldp);
+ } else {
+ BSN bsn = new BSN(BSN.BSN_TYPE_BDDP);
+ bsn.setPayload(lldp);
+
+ ethernet = new Ethernet()
+ .setSourceMACAddress(ofpPort.getHardwareAddress())
+ .setDestinationMACAddress(LLDP_BSN_DST_MAC_STRING)
+ .setEtherType(Ethernet.TYPE_BSN);
+ ethernet.setPayload(bsn);
+ }
+
+
+ // serialize and wrap in a packet out
+ byte[] data = ethernet.serialize();
+ OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
+ po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
+ po.setInPort(OFPort.OFPP_NONE);
+
+ // set actions
+ List<OFAction> actions = new ArrayList<OFAction>();
+ actions.add(new OFActionOutput(port, (short) 0));
+ po.setActions(actions);
+ po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
+
+ // set data
+ po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLength() + data.length);
+ po.setPacketData(data);
+
+ // send
+ try {
+ iofSwitch.write(po, null);
+ iofSwitch.flush();
+ } catch (IOException e) {
+ log.error("Failure sending LLDP out port {} on switch {}",
+ new Object[]{ port, iofSwitch.getStringId() }, e);
+ }
+
+ }
+
+ /**
+ * Send LLDPs to all switch-ports
+ */
+ protected void discoverOnAllPorts() {
+ if (log.isTraceEnabled()) {
+ log.trace("Sending LLDP packets out of all the enabled ports on switch {}");
+ }
+ Set<Long> switches = floodlightProvider.getSwitches().keySet();
+ // Send standard LLDPs
+ for (long sw: switches) {
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) continue;
+ if (iofSwitch.getEnabledPorts() != null) {
+ for (OFPhysicalPort ofp: iofSwitch.getEnabledPorts()) {
+ if (isLinkDiscoverySuppressed(sw, ofp.getPortNumber()))
+ continue;
+ if (autoPortFastFeature && isFastPort(sw, ofp.getPortNumber()))
+ continue;
+
+ // sends forward LLDP only non-fastports.
+ sendDiscoveryMessage(sw, ofp.getPortNumber(), true, false);
+
+ // If the switch port is not alreayd in the maintenance
+ // queue, add it.
+ NodePortTuple npt = new NodePortTuple(sw, ofp.getPortNumber());
+ addToMaintenanceQueue(npt);
+ }
+ }
+ }
+ }
+
+ protected void setControllerTLV() {
+ //Setting the controllerTLVValue based on current nano time,
+ //controller's IP address, and the network interface object hash
+ //the corresponding IP address.
+
+ final int prime = 7867;
+ InetAddress localIPAddress = null;
+ NetworkInterface localInterface = null;
+
+ byte[] controllerTLVValue = new byte[] {0, 0, 0, 0, 0, 0, 0, 0}; // 8 byte value.
+ ByteBuffer bb = ByteBuffer.allocate(10);
+
+ try{
+ localIPAddress = java.net.InetAddress.getLocalHost();
+ localInterface = NetworkInterface.getByInetAddress(localIPAddress);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ long result = System.nanoTime();
+ if (localIPAddress != null)
+ result = result * prime + IPv4.toIPv4Address(localIPAddress.getHostAddress());
+ if (localInterface != null)
+ result = result * prime + localInterface.hashCode();
+ // set the first 4 bits to 0.
+ result = result & (0x0fffffffffffffffL);
+
+ bb.putLong(result);
+
+ bb.rewind();
+ bb.get(controllerTLVValue, 0, 8);
+
+ this.controllerTLV = new LLDPTLV().setType((byte) 0x0c).setLength((short) controllerTLVValue.length).setValue(controllerTLVValue);
+ }
+
+ @Override
+ public String getName() {
+ return "linkdiscovery";
+ }
+
+ @Override
+ public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+ switch (msg.getType()) {
+ case PACKET_IN:
+ return this.handlePacketIn(sw.getId(), (OFPacketIn) msg, cntx);
+ case PORT_STATUS:
+ return this.handlePortStatus(sw.getId(), (OFPortStatus) msg);
+ default:
+ break;
+ }
+ return Command.CONTINUE;
+ }
+
+ private Command handleLldp(LLDP lldp, long sw, OFPacketIn pi, boolean isStandard, FloodlightContext cntx) {
+ // If LLDP is suppressed on this port, ignore received packet as well
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) {
+ return Command.STOP;
+ }
+
+ if (isLinkDiscoverySuppressed(sw, pi.getInPort()))
+ return Command.STOP;
+
+ // If this is a malformed LLDP, or not from us, exit
+ if (lldp.getPortId() == null || lldp.getPortId().getLength() != 3)
+ return Command.CONTINUE;
+
+ long myId = ByteBuffer.wrap(controllerTLV.getValue()).getLong();
+ long otherId = 0;
+ boolean myLLDP = false;
+ Boolean isReverse = null;
+
+ ByteBuffer portBB = ByteBuffer.wrap(lldp.getPortId().getValue());
+ portBB.position(1);
+
+ Short remotePort = portBB.getShort();
+ IOFSwitch remoteSwitch = null;
+
+ // Verify this LLDP packet matches what we're looking for
+ for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) {
+ if (lldptlv.getType() == 127 && lldptlv.getLength() == 12 &&
+ lldptlv.getValue()[0] == 0x0 && lldptlv.getValue()[1] == 0x26 &&
+ lldptlv.getValue()[2] == (byte)0xe1 && lldptlv.getValue()[3] == 0x0) {
+ ByteBuffer dpidBB = ByteBuffer.wrap(lldptlv.getValue());
+ remoteSwitch = floodlightProvider.getSwitches().get(dpidBB.getLong(4));
+ if (remoteSwitch == null) {
+ // Added by ONOS
+ // floodlight LLDP coming from a remote switch connected to a different controller
+ // add it to our cache of unconnected remote switches
+ remoteSwitch = addRemoteSwitch(dpidBB.getLong(4), remotePort);
+ }
+ } else if (lldptlv.getType() == 12 && lldptlv.getLength() == 8){
+ otherId = ByteBuffer.wrap(lldptlv.getValue()).getLong();
+ if (myId == otherId)
+ myLLDP = true;
+ } else if (lldptlv.getType() == TLV_DIRECTION_TYPE &&
+ lldptlv.getLength() == TLV_DIRECTION_LENGTH) {
+ if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_FORWARD[0])
+ isReverse = false;
+ else if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_REVERSE[0])
+ isReverse = true;
+ }
+ }
+
+ if (myLLDP == false) {
+ // This is not the LLDP sent by this controller.
+ // If the LLDP message has multicast bit set, then we need to broadcast
+ // the packet as a regular packet.
+ if (isStandard) {
+ if (log.isTraceEnabled()) {
+ log.trace("Getting standard LLDP from a different controller and quelching it.");
+ }
+ return Command.STOP;
+ }
+ else if (myId < otherId) {
+ if (log.isTraceEnabled()) {
+ log.trace("Getting BDDP packets from a different controller" +
+ "and letting it go through normal processing chain.");
+ }
+ //XXX ONOS: Fix the BDDP broadcast issue
+ //return Command.CONTINUE;
+ return Command.STOP;
+ }
+ }
+
+
+ if (remoteSwitch == null) {
+ // Ignore LLDPs not generated by Floodlight, or from a switch that has recently
+ // disconnected, or from a switch connected to another Floodlight instance
+ if (log.isTraceEnabled()) {
+ log.trace("Received LLDP from remote switch not connected to the controller");
+ }
+ return Command.STOP;
+ }
+
+ if (!remoteSwitch.portEnabled(remotePort)) {
+ if (log.isTraceEnabled()) {
+ log.trace("Ignoring link with disabled source port: switch {} port {}", remoteSwitch, remotePort);
+ }
+ return Command.STOP;
+ }
+ if (suppressLinkDiscovery.contains(new NodePortTuple(remoteSwitch.getId(),
+ remotePort))) {
+ if (log.isTraceEnabled()) {
+ log.trace("Ignoring link with suppressed src port: switch {} port {}",
+ remoteSwitch, remotePort);
+ }
+ return Command.STOP;
+ }
+ if (!iofSwitch.portEnabled(pi.getInPort())) {
+ if (log.isTraceEnabled()) {
+ log.trace("Ignoring link with disabled dest port: switch {} port {}", sw, pi.getInPort());
+ }
+ return Command.STOP;
+ }
+
+ OFPhysicalPort physicalPort = remoteSwitch.getPort(remotePort);
+ int srcPortState = (physicalPort != null) ? physicalPort.getState() : 0;
+ physicalPort = iofSwitch.getPort(pi.getInPort());
+ int dstPortState = (physicalPort != null) ? physicalPort.getState() : 0;
+
+ // Store the time of update to this link, and push it out to routingEngine
+ Link lt = new Link(remoteSwitch.getId(), remotePort, iofSwitch.getId(), pi.getInPort());
+
+
+ Long lastLldpTime = null;
+ Long lastBddpTime = null;
+
+ Long firstSeenTime = System.currentTimeMillis();
+
+ if (isStandard)
+ lastLldpTime = System.currentTimeMillis();
+ else
+ lastBddpTime = System.currentTimeMillis();
+
+ LinkInfo newLinkInfo =
+ new LinkInfo(firstSeenTime, lastLldpTime, lastBddpTime,
+ srcPortState, dstPortState);
+
+ addOrUpdateLink(lt, newLinkInfo);
+
+ // Check if reverse link exists.
+ // If it doesn't exist and if the forward link was seen
+ // first seen within a small interval, send probe on the
+ // reverse link.
+
+ newLinkInfo = links.get(lt);
+ if (newLinkInfo != null && isStandard && isReverse == false) {
+ Link reverseLink = new Link(lt.getDst(), lt.getDstPort(),
+ lt.getSrc(), lt.getSrcPort());
+ LinkInfo reverseInfo = links.get(reverseLink);
+ if (reverseInfo == null) {
+ // the reverse link does not exist.
+ if (newLinkInfo.getFirstSeenTime() > System.currentTimeMillis() - LINK_TIMEOUT) {
+ this.sendDiscoveryMessage(lt.getDst(), lt.getDstPort(), isStandard, true);
+ }
+ }
+ }
+
+ // If the received packet is a BDDP packet, then create a reverse BDDP
+ // link as well.
+ if (!isStandard) {
+ Link reverseLink = new Link(lt.getDst(), lt.getDstPort(),
+ lt.getSrc(), lt.getSrcPort());
+
+ // srcPortState and dstPort state are reversed.
+ LinkInfo reverseInfo =
+ new LinkInfo(firstSeenTime, lastLldpTime, lastBddpTime,
+ dstPortState, srcPortState);
+
+ addOrUpdateLink(reverseLink, reverseInfo);
+ }
+
+ // Remove the node ports from the quarantine and maintenance queues.
+ NodePortTuple nptSrc = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+ NodePortTuple nptDst = new NodePortTuple(lt.getDst(), lt.getDstPort());
+ removeFromQuarantineQueue(nptSrc);
+ removeFromMaintenanceQueue(nptSrc);
+ removeFromQuarantineQueue(nptDst);
+ removeFromMaintenanceQueue(nptDst);
+
+ // Consume this message
+ return Command.STOP;
+ }
+
+ protected Command handlePacketIn(long sw, OFPacketIn pi,
+ FloodlightContext cntx) {
+ Ethernet eth =
+ IFloodlightProviderService.bcStore.get(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+
+ if(eth.getEtherType() == Ethernet.TYPE_BSN) {
+ BSN bsn = (BSN) eth.getPayload();
+ if (bsn == null) return Command.STOP;
+ if (bsn.getPayload() == null) return Command.STOP;
+ // It could be a packet other than BSN LLDP, therefore
+ // continue with the regular processing.
+ if (bsn.getPayload() instanceof LLDP == false)
+ return Command.CONTINUE;
+ return handleLldp((LLDP) bsn.getPayload(), sw, pi, false, cntx);
+ } else if (eth.getEtherType() == Ethernet.TYPE_LLDP) {
+ return handleLldp((LLDP) eth.getPayload(), sw, pi, true, cntx);
+ } else if (eth.getEtherType() < 1500) {
+ long destMac = eth.getDestinationMAC().toLong();
+ if ((destMac & LINK_LOCAL_MASK) == LINK_LOCAL_VALUE){
+ if (log.isTraceEnabled()) {
+ log.trace("Ignoring packet addressed to 802.1D/Q " +
+ "reserved address.");
+ }
+ return Command.STOP;
+ }
+ }
+
+ // If packet-in is from a quarantine port, stop processing.
+ NodePortTuple npt = new NodePortTuple(sw, pi.getInPort());
+ if (quarantineQueue.contains(npt)) return Command.STOP;
+
+ return Command.CONTINUE;
+ }
+
+ protected UpdateOperation getUpdateOperation(int srcPortState,
+ int dstPortState) {
+ boolean added =
+ (((srcPortState &
+ OFPortState.OFPPS_STP_MASK.getValue()) !=
+ OFPortState.OFPPS_STP_BLOCK.getValue()) &&
+ ((dstPortState &
+ OFPortState.OFPPS_STP_MASK.getValue()) !=
+ OFPortState.OFPPS_STP_BLOCK.getValue()));
+
+ if (added) return UpdateOperation.LINK_UPDATED;
+ return UpdateOperation.LINK_REMOVED;
+ }
+
+
+
+ protected UpdateOperation getUpdateOperation(int srcPortState) {
+ boolean portUp = ((srcPortState &
+ OFPortState.OFPPS_STP_MASK.getValue()) !=
+ OFPortState.OFPPS_STP_BLOCK.getValue());
+
+ if (portUp) return UpdateOperation.PORT_UP;
+ else return UpdateOperation.PORT_DOWN;
+ }
+
+ protected boolean addOrUpdateLink(Link lt, LinkInfo newInfo) {
+
+ NodePortTuple srcNpt, dstNpt;
+ boolean linkChanged = false;
+
+ lock.writeLock().lock();
+ try {
+ // put the new info. if an old info exists, it will be returned.
+ LinkInfo oldInfo = links.put(lt, newInfo);
+ if (oldInfo != null &&
+ oldInfo.getFirstSeenTime() < newInfo.getFirstSeenTime())
+ newInfo.setFirstSeenTime(oldInfo.getFirstSeenTime());
+
+ if (log.isTraceEnabled()) {
+ log.trace("addOrUpdateLink: {} {}",
+ lt,
+ (newInfo.getMulticastValidTime()!=null) ? "multicast" : "unicast");
+ }
+
+ UpdateOperation updateOperation = null;
+ linkChanged = false;
+
+ srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+ dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+ if (oldInfo == null) {
+ // index it by switch source
+ if (!switchLinks.containsKey(lt.getSrc()))
+ switchLinks.put(lt.getSrc(), new HashSet<Link>());
+ switchLinks.get(lt.getSrc()).add(lt);
+
+ // index it by switch dest
+ if (!switchLinks.containsKey(lt.getDst()))
+ switchLinks.put(lt.getDst(), new HashSet<Link>());
+ switchLinks.get(lt.getDst()).add(lt);
+
+ // index both ends by switch:port
+ if (!portLinks.containsKey(srcNpt))
+ portLinks.put(srcNpt, new HashSet<Link>());
+ portLinks.get(srcNpt).add(lt);
+
+ if (!portLinks.containsKey(dstNpt))
+ portLinks.put(dstNpt, new HashSet<Link>());
+ portLinks.get(dstNpt).add(lt);
+
+ // Add to portNOFLinks if the unicast valid time is null
+ if (newInfo.getUnicastValidTime() == null)
+ addLinkToBroadcastDomain(lt);
+
+ writeLinkToStorage(lt, newInfo);
+
+ // ONOS: Distinguish added event separately from updated event
+ updateOperation = UpdateOperation.LINK_ADDED;
+ linkChanged = true;
+
+ // Add to event history
+ evHistTopoLink(lt.getSrc(),
+ lt.getDst(),
+ lt.getSrcPort(),
+ lt.getDstPort(),
+ newInfo.getSrcPortState(), newInfo.getDstPortState(),
+ getLinkType(lt, newInfo),
+ EvAction.LINK_ADDED, "LLDP Recvd");
+ } else {
+ // Since the link info is already there, we need to
+ // update the right fields.
+ if (newInfo.getUnicastValidTime() == null) {
+ // This is due to a multicast LLDP, so copy the old unicast
+ // value.
+ if (oldInfo.getUnicastValidTime() != null) {
+ newInfo.setUnicastValidTime(oldInfo.getUnicastValidTime());
+ }
+ } else if (newInfo.getMulticastValidTime() == null) {
+ // This is due to a unicast LLDP, so copy the old multicast
+ // value.
+ if (oldInfo.getMulticastValidTime() != null) {
+ newInfo.setMulticastValidTime(oldInfo.getMulticastValidTime());
+ }
+ }
+
+ Long oldTime = oldInfo.getUnicastValidTime();
+ Long newTime = newInfo.getUnicastValidTime();
+ // the link has changed its state between openflow and non-openflow
+ // if the unicastValidTimes are null or not null
+ if (oldTime != null & newTime == null) {
+ // openflow -> non-openflow transition
+ // we need to add the link tuple to the portNOFLinks
+ addLinkToBroadcastDomain(lt);
+ linkChanged = true;
+ } else if (oldTime == null & newTime != null) {
+ // non-openflow -> openflow transition
+ // we need to remove the link from the portNOFLinks
+ removeLinkFromBroadcastDomain(lt);
+ linkChanged = true;
+ }
+
+ // Only update the port states if they've changed
+ if (newInfo.getSrcPortState().intValue() !=
+ oldInfo.getSrcPortState().intValue() ||
+ newInfo.getDstPortState().intValue() !=
+ oldInfo.getDstPortState().intValue())
+ linkChanged = true;
+
+ // Write changes to storage. This will always write the updated
+ // valid time, plus the port states if they've changed (i.e. if
+ // they weren't set to null in the previous block of code.
+ writeLinkToStorage(lt, newInfo);
+
+ if (linkChanged) {
+ updateOperation = getUpdateOperation(newInfo.getSrcPortState(),
+ newInfo.getDstPortState());
+ if (log.isTraceEnabled()) {
+ log.trace("Updated link {}", lt);
+ }
+ // Add to event history
+ evHistTopoLink(lt.getSrc(),
+ lt.getDst(),
+ lt.getSrcPort(),
+ lt.getDstPort(),
+ newInfo.getSrcPortState(), newInfo.getDstPortState(),
+ getLinkType(lt, newInfo),
+ EvAction.LINK_PORT_STATE_UPDATED,
+ "LLDP Recvd");
+ }
+ }
+
+ if (linkChanged) {
+ // find out if the link was added or removed here.
+ updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+ lt.getDst(), lt.getDstPort(),
+ getLinkType(lt, newInfo),
+ updateOperation));
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+
+ return linkChanged;
+ }
+
+ public Map<Long, Set<Link>> getSwitchLinks() {
+ return this.switchLinks;
+ }
+
+ /**
+ * Removes links from memory and storage.
+ * @param links The List of @LinkTuple to delete.
+ */
+ protected void deleteLinks(List<Link> links, String reason) {
+ NodePortTuple srcNpt, dstNpt;
+
+ lock.writeLock().lock();
+ try {
+ for (Link lt : links) {
+ srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+ dstNpt =new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+ switchLinks.get(lt.getSrc()).remove(lt);
+ switchLinks.get(lt.getDst()).remove(lt);
+ if (switchLinks.containsKey(lt.getSrc()) &&
+ switchLinks.get(lt.getSrc()).isEmpty())
+ this.switchLinks.remove(lt.getSrc());
+ if (this.switchLinks.containsKey(lt.getDst()) &&
+ this.switchLinks.get(lt.getDst()).isEmpty())
+ this.switchLinks.remove(lt.getDst());
+
+ if (this.portLinks.get(srcNpt) != null) {
+ this.portLinks.get(srcNpt).remove(lt);
+ if (this.portLinks.get(srcNpt).isEmpty())
+ this.portLinks.remove(srcNpt);
+ }
+ if (this.portLinks.get(dstNpt) != null) {
+ this.portLinks.get(dstNpt).remove(lt);
+ if (this.portLinks.get(dstNpt).isEmpty())
+ this.portLinks.remove(dstNpt);
+ }
+
+ LinkInfo info = this.links.remove(lt);
+ updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+ lt.getDst(), lt.getDstPort(),
+ getLinkType(lt, info),
+ UpdateOperation.LINK_REMOVED));
+
+ // Update Event History
+ evHistTopoLink(lt.getSrc(),
+ lt.getDst(),
+ lt.getSrcPort(),
+ lt.getDstPort(),
+ 0, 0, // Port states
+ ILinkDiscovery.LinkType.INVALID_LINK,
+ EvAction.LINK_DELETED, reason);
+
+ // remove link from storage.
+ removeLinkFromStorage(lt);
+
+ // TODO Whenever link is removed, it has to checked if
+ // the switchports must be added to quarantine.
+
+ if (log.isTraceEnabled()) {
+ log.trace("Deleted link {}", lt);
+ }
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Handles an OFPortStatus message from a switch. We will add or
+ * delete LinkTupes as well re-compute the topology if needed.
+ * @param sw The IOFSwitch that sent the port status message
+ * @param ps The OFPortStatus message
+ * @return The Command to continue or stop after we process this message
+ */
+ protected Command handlePortStatus(long sw, OFPortStatus ps) {
+
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) return Command.CONTINUE;
+
+ // ONOS: If we do not control this switch, then we should not process its port status messages
+ if (!registryService.hasControl(iofSwitch.getId())) return Command.CONTINUE;
+
+ if (log.isTraceEnabled()) {
+ log.trace("handlePortStatus: Switch {} port #{} reason {}; " +
+ "config is {} state is {}",
+ new Object[] {iofSwitch.getStringId(),
+ ps.getDesc().getPortNumber(),
+ ps.getReason(),
+ ps.getDesc().getConfig(),
+ ps.getDesc().getState()});
+ }
+
+ short port = ps.getDesc().getPortNumber();
+ NodePortTuple npt = new NodePortTuple(sw, port);
+ boolean linkDeleted = false;
+ boolean linkInfoChanged = false;
+
+ lock.writeLock().lock();
+ try {
+ // if ps is a delete, or a modify where the port is down or
+ // configured down
+ if ((byte)OFPortReason.OFPPR_DELETE.ordinal() == ps.getReason() ||
+ ((byte)OFPortReason.OFPPR_MODIFY.ordinal() ==
+ ps.getReason() && !portEnabled(ps.getDesc()))) {
+ deleteLinksOnPort(npt, "Port Status Changed");
+ LDUpdate update = new LDUpdate(sw, port, UpdateOperation.PORT_DOWN);
+ updates.add(update);
+ linkDeleted = true;
+ }
+ else if (ps.getReason() ==
+ (byte)OFPortReason.OFPPR_MODIFY.ordinal()) {
+ // If ps is a port modification and the port state has changed
+ // that affects links in the topology
+
+ if (this.portLinks.containsKey(npt)) {
+ for (Link lt: this.portLinks.get(npt)) {
+ LinkInfo linkInfo = links.get(lt);
+ assert(linkInfo != null);
+ Integer updatedSrcPortState = null;
+ Integer updatedDstPortState = null;
+ if (lt.getSrc() == npt.getNodeId() &&
+ lt.getSrcPort() == npt.getPortId() &&
+ (linkInfo.getSrcPortState() !=
+ ps.getDesc().getState())) {
+ updatedSrcPortState = ps.getDesc().getState();
+ linkInfo.setSrcPortState(updatedSrcPortState);
+ }
+ if (lt.getDst() == npt.getNodeId() &&
+ lt.getDstPort() == npt.getPortId() &&
+ (linkInfo.getDstPortState() !=
+ ps.getDesc().getState())) {
+ updatedDstPortState = ps.getDesc().getState();
+ linkInfo.setDstPortState(updatedDstPortState);
+ }
+ if ((updatedSrcPortState != null) ||
+ (updatedDstPortState != null)) {
+ // The link is already known to link discovery
+ // manager and the status has changed, therefore
+ // send an LDUpdate.
+ UpdateOperation operation =
+ getUpdateOperation(linkInfo.getSrcPortState(),
+ linkInfo.getDstPortState());
+ updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+ lt.getDst(), lt.getDstPort(),
+ getLinkType(lt, linkInfo),
+ operation));
+ writeLinkToStorage(lt, linkInfo);
+ linkInfoChanged = true;
+ }
+ }
+ }
+
+ UpdateOperation operation =
+ getUpdateOperation(ps.getDesc().getState());
+ updates.add(new LDUpdate(sw, port, operation));
+ }
+
+ if (!linkDeleted && !linkInfoChanged){
+ if (log.isTraceEnabled()) {
+ log.trace("handlePortStatus: Switch {} port #{} reason {};"+
+ " no links to update/remove",
+ new Object[] {HexString.toHexString(sw),
+ ps.getDesc().getPortNumber(),
+ ps.getReason()});
+ }
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+
+ if (!linkDeleted) {
+ // Send LLDP right away when port state is changed for faster
+ // cluster-merge. If it is a link delete then there is not need
+ // to send the LLDPs right away and instead we wait for the LLDPs
+ // to be sent on the timer as it is normally done
+ // do it outside the write-lock
+ // sendLLDPTask.reschedule(1000, TimeUnit.MILLISECONDS);
+ processNewPort(npt.getNodeId(), npt.getPortId());
+ }
+ return Command.CONTINUE;
+ }
+
+ /**
+ * Process a new port.
+ * If link discovery is disabled on the port, then do nothing.
+ * If autoportfast feature is enabled and the port is a fast port, then
+ * do nothing.
+ * Otherwise, send LLDP message. Add the port to quarantine.
+ * @param sw
+ * @param p
+ */
+ private void processNewPort(long sw, short p) {
+ if (isLinkDiscoverySuppressed(sw, p)) {
+ // Do nothing as link discovery is suppressed.
+ }
+ else if (autoPortFastFeature && isFastPort(sw, p)) {
+ // Do nothing as the port is a fast port.
+ }
+ else {
+ NodePortTuple npt = new NodePortTuple(sw, p);
+ discover(sw, p);
+ // if it is not a fast port, add it to quarantine.
+ if (!isFastPort(sw, p)) {
+ addToQuarantineQueue(npt);
+ } else {
+ // Add to maintenance queue to ensure that BDDP packets
+ // are sent out.
+ addToMaintenanceQueue(npt);
+ }
+ }
+ }
+
+ /**
+ * We send out LLDP messages when a switch is added to discover the topology
+ * @param sw The IOFSwitch that connected to the controller
+ */
+ @Override
+ public void addedSwitch(IOFSwitch sw) {
+
+ if (sw.getEnabledPorts() != null) {
+ for (Short p : sw.getEnabledPortNumbers()) {
+ processNewPort(sw.getId(), p);
+ }
+ }
+ // Update event history
+ evHistTopoSwitch(sw, EvAction.SWITCH_CONNECTED, "None");
+ LDUpdate update = new LDUpdate(sw.getId(), null,
+ UpdateOperation.SWITCH_UPDATED);
+ updates.add(update);
+ }
+
+ /**
+ * When a switch disconnects we remove any links from our map and notify.
+ * @param The id of the switch
+ */
+ @Override
+ public void removedSwitch(IOFSwitch iofSwitch) {
+ // Update event history
+ long sw = iofSwitch.getId();
+ evHistTopoSwitch(iofSwitch, EvAction.SWITCH_DISCONNECTED, "None");
+ List<Link> eraseList = new ArrayList<Link>();
+ lock.writeLock().lock();
+ try {
+ if (switchLinks.containsKey(sw)) {
+ if (log.isTraceEnabled()) {
+ log.trace("Handle switchRemoved. Switch {}; removing links {}",
+ HexString.toHexString(sw), switchLinks.get(sw));
+ }
+ // add all tuples with an endpoint on this switch to erase list
+ eraseList.addAll(switchLinks.get(sw));
+ deleteLinks(eraseList, "Switch Removed");
+
+ // Send a switch removed update
+ LDUpdate update = new LDUpdate(sw, null, UpdateOperation.SWITCH_REMOVED);
+ updates.add(update);
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * We don't react the port changed notifications here. we listen for
+ * OFPortStatus messages directly. Might consider using this notifier
+ * instead
+ */
+ @Override
+ public void switchPortChanged(Long switchId) {
+ // no-op
+ }
+
+ /**
+ * Delete links incident on a given switch port.
+ * @param npt
+ * @param reason
+ */
+ protected void deleteLinksOnPort(NodePortTuple npt, String reason) {
+ List<Link> eraseList = new ArrayList<Link>();
+ if (this.portLinks.containsKey(npt)) {
+ if (log.isTraceEnabled()) {
+ log.trace("handlePortStatus: Switch {} port #{} " +
+ "removing links {}",
+ new Object[] {HexString.toHexString(npt.getNodeId()),
+ npt.getPortId(),
+ this.portLinks.get(npt)});
+ }
+ eraseList.addAll(this.portLinks.get(npt));
+ deleteLinks(eraseList, reason);
+ }
+ }
+
+ /**
+ * Iterates through the list of links and deletes if the
+ * last discovery message reception time exceeds timeout values.
+ */
+ protected void timeoutLinks() {
+ List<Link> eraseList = new ArrayList<Link>();
+ Long curTime = System.currentTimeMillis();
+ boolean linkChanged = false;
+
+ // reentrant required here because deleteLink also write locks
+ lock.writeLock().lock();
+ try {
+ Iterator<Entry<Link, LinkInfo>> it =
+ this.links.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry<Link, LinkInfo> entry = it.next();
+ Link lt = entry.getKey();
+ LinkInfo info = entry.getValue();
+
+ // Timeout the unicast and multicast LLDP valid times
+ // independently.
+ if ((info.getUnicastValidTime() != null) &&
+ (info.getUnicastValidTime() + (this.LINK_TIMEOUT * 1000) < curTime)){
+ info.setUnicastValidTime(null);
+
+ if (info.getMulticastValidTime() != null)
+ addLinkToBroadcastDomain(lt);
+ // Note that even if mTime becomes null later on,
+ // the link would be deleted, which would trigger updateClusters().
+ linkChanged = true;
+ }
+ if ((info.getMulticastValidTime()!= null) &&
+ (info.getMulticastValidTime()+ (this.LINK_TIMEOUT * 1000) < curTime)) {
+ info.setMulticastValidTime(null);
+ // if uTime is not null, then link will remain as openflow
+ // link. If uTime is null, it will be deleted. So, we
+ // don't care about linkChanged flag here.
+ removeLinkFromBroadcastDomain(lt);
+ linkChanged = true;
+ }
+ // Add to the erase list only if the unicast
+ // time is null.
+ if (info.getUnicastValidTime() == null &&
+ info.getMulticastValidTime() == null){
+ eraseList.add(entry.getKey());
+ } else if (linkChanged) {
+ UpdateOperation operation;
+ operation = getUpdateOperation(info.getSrcPortState(),
+ info.getDstPortState());
+ updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+ lt.getDst(), lt.getDstPort(),
+ getLinkType(lt, info),
+ operation));
+ }
+ }
+
+ // if any link was deleted or any link was changed.
+ if ((eraseList.size() > 0) || linkChanged) {
+ deleteLinks(eraseList, "LLDP timeout");
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ private boolean portEnabled(OFPhysicalPort port) {
+ if (port == null)
+ return false;
+ if ((OFPortConfig.OFPPC_PORT_DOWN.getValue() & port.getConfig()) > 0)
+ return false;
+ if ((OFPortState.OFPPS_LINK_DOWN.getValue() & port.getState()) > 0)
+ return false;
+ // Port STP state doesn't work with multiple VLANs, so ignore it for now
+ // if ((port.getState() & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue())
+ // return false;
+ return true;
+ }
+
+ public Map<NodePortTuple, Set<Link>> getPortBroadcastDomainLinks() {
+ return portBroadcastDomainLinks;
+ }
+
+ @Override
+ public Map<Link, LinkInfo> getLinks() {
+ lock.readLock().lock();
+ Map<Link, LinkInfo> result;
+ try {
+ result = new HashMap<Link, LinkInfo>(links);
+ } finally {
+ lock.readLock().unlock();
+ }
+ return result;
+ }
+
+ protected void addLinkToBroadcastDomain(Link lt) {
+
+ NodePortTuple srcNpt, dstNpt;
+ srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+ dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+ if (!portBroadcastDomainLinks.containsKey(lt.getSrc()))
+ portBroadcastDomainLinks.put(srcNpt, new HashSet<Link>());
+ portBroadcastDomainLinks.get(srcNpt).add(lt);
+
+ if (!portBroadcastDomainLinks.containsKey(lt.getDst()))
+ portBroadcastDomainLinks.put(dstNpt, new HashSet<Link>());
+ portBroadcastDomainLinks.get(dstNpt).add(lt);
+ }
+
+ protected void removeLinkFromBroadcastDomain(Link lt) {
+
+ NodePortTuple srcNpt, dstNpt;
+ srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+ dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+ if (portBroadcastDomainLinks.containsKey(srcNpt)) {
+ portBroadcastDomainLinks.get(srcNpt).remove(lt);
+ if (portBroadcastDomainLinks.get(srcNpt).isEmpty())
+ portBroadcastDomainLinks.remove(srcNpt);
+ }
+
+ if (portBroadcastDomainLinks.containsKey(dstNpt)) {
+ portBroadcastDomainLinks.get(dstNpt).remove(lt);
+ if (portBroadcastDomainLinks.get(dstNpt).isEmpty())
+ portBroadcastDomainLinks.remove(dstNpt);
+ }
+ }
+
+ // STORAGE METHODS
+ /**
+ * Deletes all links from storage
+ */
+ void clearAllLinks() {
+ storageSource.deleteRowsAsync(LINK_TABLE_NAME, null);
+ }
+
+ /**
+ * Gets the storage key for a LinkTuple
+ * @param lt The LinkTuple to get
+ * @return The storage key as a String
+ */
+ private String getLinkId(Link lt) {
+ return HexString.toHexString(lt.getSrc()) +
+ "-" + lt.getSrcPort() + "-" +
+ HexString.toHexString(lt.getDst())+
+ "-" + lt.getDstPort();
+ }
+
+ /**
+ * Writes a LinkTuple and corresponding LinkInfo to storage
+ * @param lt The LinkTuple to write
+ * @param linkInfo The LinkInfo to write
+ */
+ protected void writeLinkToStorage(Link lt, LinkInfo linkInfo) {
+ LinkType type = getLinkType(lt, linkInfo);
+
+ // Write only direct links. Do not write links to external
+ // L2 network.
+ // if (type != LinkType.DIRECT_LINK && type != LinkType.TUNNEL) {
+ // return;
+ // }
+
+ Map<String, Object> rowValues = new HashMap<String, Object>();
+ String id = getLinkId(lt);
+ rowValues.put(LINK_ID, id);
+ rowValues.put(LINK_VALID_TIME, linkInfo.getUnicastValidTime());
+ String srcDpid = HexString.toHexString(lt.getSrc());
+ rowValues.put(LINK_SRC_SWITCH, srcDpid);
+ rowValues.put(LINK_SRC_PORT, lt.getSrcPort());
+
+ if (type == LinkType.DIRECT_LINK)
+ rowValues.put(LINK_TYPE, "internal");
+ else if (type == LinkType.MULTIHOP_LINK)
+ rowValues.put(LINK_TYPE, "external");
+ else if (type == LinkType.TUNNEL)
+ rowValues.put(LINK_TYPE, "tunnel");
+ else rowValues.put(LINK_TYPE, "invalid");
+
+ if (linkInfo.linkStpBlocked()) {
+ if (log.isTraceEnabled()) {
+ log.trace("writeLink, link {}, info {}, srcPortState Blocked",
+ lt, linkInfo);
+ }
+ rowValues.put(LINK_SRC_PORT_STATE,
+ OFPhysicalPort.OFPortState.OFPPS_STP_BLOCK.getValue());
+ } else {
+ if (log.isTraceEnabled()) {
+ log.trace("writeLink, link {}, info {}, srcPortState {}",
+ new Object[]{ lt, linkInfo, linkInfo.getSrcPortState() });
+ }
+ rowValues.put(LINK_SRC_PORT_STATE, linkInfo.getSrcPortState());
+ }
+ String dstDpid = HexString.toHexString(lt.getDst());
+ rowValues.put(LINK_DST_SWITCH, dstDpid);
+ rowValues.put(LINK_DST_PORT, lt.getDstPort());
+ if (linkInfo.linkStpBlocked()) {
+ if (log.isTraceEnabled()) {
+ log.trace("writeLink, link {}, info {}, dstPortState Blocked",
+ lt, linkInfo);
+ }
+ rowValues.put(LINK_DST_PORT_STATE,
+ OFPhysicalPort.OFPortState.OFPPS_STP_BLOCK.getValue());
+ } else {
+ if (log.isTraceEnabled()) {
+ log.trace("writeLink, link {}, info {}, dstPortState {}",
+ new Object[]{ lt, linkInfo, linkInfo.getDstPortState() });
+ }
+ rowValues.put(LINK_DST_PORT_STATE, linkInfo.getDstPortState());
+ }
+ storageSource.updateRowAsync(LINK_TABLE_NAME, rowValues);
+ }
+
+ public Long readLinkValidTime(Link lt) {
+ // FIXME: We're not currently using this right now, but if we start
+ // to use this again, we probably shouldn't use it in its current
+ // form, because it's doing synchronous storage calls. Depending
+ // on the context this may still be OK, but if it's being called
+ // on the packet in processing thread it should be reworked to
+ // use asynchronous storage calls.
+ Long validTime = null;
+ IResultSet resultSet = null;
+ try {
+ String[] columns = { LINK_VALID_TIME };
+ String id = getLinkId(lt);
+ resultSet = storageSource.executeQuery(LINK_TABLE_NAME, columns,
+ new OperatorPredicate(LINK_ID, OperatorPredicate.Operator.EQ, id), null);
+ if (resultSet.next())
+ validTime = resultSet.getLong(LINK_VALID_TIME);
+ }
+ finally {
+ if (resultSet != null)
+ resultSet.close();
+ }
+ return validTime;
+ }
+
+ /**
+ * Removes a link from storage using an asynchronous call.
+ * @param lt The LinkTuple to delete.
+ */
+ protected void removeLinkFromStorage(Link lt) {
+ String id = getLinkId(lt);
+ storageSource.deleteRowAsync(LINK_TABLE_NAME, id);
+ }
+
+ @Override
+ public void addListener(ILinkDiscoveryListener listener) {
+ linkDiscoveryAware.add(listener);
+ }
+
+ /**
+ * Register a link discovery aware component
+ * @param linkDiscoveryAwareComponent
+ */
+ public void addLinkDiscoveryAware(ILinkDiscoveryListener linkDiscoveryAwareComponent) {
+ // TODO make this a copy on write set or lock it somehow
+ this.linkDiscoveryAware.add(linkDiscoveryAwareComponent);
+ }
+
+ /**
+ * Deregister a link discovery aware component
+ * @param linkDiscoveryAwareComponent
+ */
+ public void removeLinkDiscoveryAware(ILinkDiscoveryListener linkDiscoveryAwareComponent) {
+ // TODO make this a copy on write set or lock it somehow
+ this.linkDiscoveryAware.remove(linkDiscoveryAwareComponent);
+ }
+
+ /**
+ * Sets the IStorageSource to use for ITology
+ * @param storageSource the storage source to use
+ */
+ public void setStorageSource(IStorageSourceService storageSource) {
+ this.storageSource = storageSource;
+ }
+
+ /**
+ * Gets the storage source for this ITopology
+ * @return The IStorageSource ITopology is writing to
+ */
+ public IStorageSourceService getStorageSource() {
+ return storageSource;
+ }
+
+ @Override
+ public boolean isCallbackOrderingPrereq(OFType type, String name) {
+ return false;
+ }
+
+ @Override
+ public boolean isCallbackOrderingPostreq(OFType type, String name) {
+ return false;
+ }
+
+ @Override
+ public void rowsModified(String tableName, Set<Object> rowKeys) {
+ Map<Long, IOFSwitch> switches = floodlightProvider.getSwitches();
+ ArrayList<IOFSwitch> updated_switches = new ArrayList<IOFSwitch>();
+ for(Object key: rowKeys) {
+ Long swId = new Long(HexString.toLong((String)key));
+ if (switches.containsKey(swId)) {
+ IOFSwitch sw = switches.get(swId);
+ boolean curr_status = sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH);
+ boolean new_status = false;
+ IResultSet resultSet = null;
+
+ try {
+ resultSet = storageSource.getRow(tableName, key);
+ for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) {
+ // In case of multiple rows, use the status in last row?
+ Map<String, Object> row = it.next().getRow();
+ if (row.containsKey(SWITCH_CONFIG_CORE_SWITCH)) {
+ new_status = ((String)row.get(SWITCH_CONFIG_CORE_SWITCH)).equals("true");
+ }
+ }
+ }
+ finally {
+ if (resultSet != null)
+ resultSet.close();
+ }
+
+ if (curr_status != new_status) {
+ updated_switches.add(sw);
+ }
+ } else {
+ if (log.isTraceEnabled()) {
+ log.trace("Update for switch which has no entry in switch " +
+ "list (dpid={}), a delete action.", (String)key);
+ }
+ }
+ }
+
+ for (IOFSwitch sw : updated_switches) {
+ // Set SWITCH_IS_CORE_SWITCH to it's inverse value
+ if (sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH)) {
+ sw.removeAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH);
+ if (log.isTraceEnabled()) {
+ log.trace("SWITCH_IS_CORE_SWITCH set to False for {}", sw);
+ }
+ updates.add(new LDUpdate(sw.getId(), SwitchType.BASIC_SWITCH,
+ UpdateOperation.SWITCH_UPDATED));
+ }
+ else {
+ sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, new Boolean(true));
+ if (log.isTraceEnabled()) {
+ log.trace("SWITCH_IS_CORE_SWITCH set to True for {}", sw);
+ }
+ updates.add(new LDUpdate(sw.getId(), SwitchType.CORE_SWITCH,
+ UpdateOperation.SWITCH_UPDATED));
+ }
+ }
+ }
+
+ @Override
+ public void rowsDeleted(String tableName, Set<Object> rowKeys) {
+ // Ignore delete events, the switch delete will do the right thing on it's own
+ }
+
+ // IFloodlightModule classes
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(ILinkDiscoveryService.class);
+ //l.add(ITopologyService.class);
+ return l;
+ }
+
+ @Override
+ public Map<Class<? extends IFloodlightService>, IFloodlightService>
+ getServiceImpls() {
+ Map<Class<? extends IFloodlightService>,
+ IFloodlightService> m =
+ new HashMap<Class<? extends IFloodlightService>,
+ IFloodlightService>();
+ // We are the class that implements the service
+ m.put(ILinkDiscoveryService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IFloodlightProviderService.class);
+ l.add(IStorageSourceService.class);
+ l.add(IThreadPoolService.class);
+ l.add(IRestApiService.class);
+ // Added by ONOS
+ l.add(IControllerRegistryService.class);
+ return l;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
+ storageSource = context.getServiceImpl(IStorageSourceService.class);
+ threadPool = context.getServiceImpl(IThreadPoolService.class);
+ restApi = context.getServiceImpl(IRestApiService.class);
+ // Added by ONOS
+ registryService = context.getServiceImpl(IControllerRegistryService.class);
+
+ // Set the autoportfast feature to false.
+ this.autoPortFastFeature = false;
+
+ // We create this here because there is no ordering guarantee
+ this.linkDiscoveryAware = new ArrayList<ILinkDiscoveryListener>();
+ this.lock = new ReentrantReadWriteLock();
+ this.updates = new LinkedBlockingQueue<LDUpdate>();
+ this.links = new HashMap<Link, LinkInfo>();
+ this.portLinks = new HashMap<NodePortTuple, Set<Link>>();
+ this.suppressLinkDiscovery =
+ Collections.synchronizedSet(new HashSet<NodePortTuple>());
+ this.portBroadcastDomainLinks = new HashMap<NodePortTuple, Set<Link>>();
+ this.switchLinks = new HashMap<Long, Set<Link>>();
+ this.quarantineQueue = new LinkedBlockingQueue<NodePortTuple>();
+ this.maintenanceQueue = new LinkedBlockingQueue<NodePortTuple>();
+ // Added by ONOS
+ this.remoteSwitches = new HashMap<Long, IOnosRemoteSwitch>();
+
+ this.evHistTopologySwitch =
+ new EventHistory<EventHistoryTopologySwitch>("Topology: Switch");
+ this.evHistTopologyLink =
+ new EventHistory<EventHistoryTopologyLink>("Topology: Link");
+ this.evHistTopologyCluster =
+ new EventHistory<EventHistoryTopologyCluster>("Topology: Cluster");
+ }
+
+ @Override
+ @LogMessageDocs({
+ @LogMessageDoc(level="ERROR",
+ message="No storage source found.",
+ explanation="Storage source was not initialized; cannot initialize " +
+ "link discovery.",
+ recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG),
+ @LogMessageDoc(level="ERROR",
+ message="Error in installing listener for " +
+ "switch config table {table}",
+ explanation="Failed to install storage notification for the " +
+ "switch config table",
+ recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG),
+ @LogMessageDoc(level="ERROR",
+ message="No storage source found.",
+ explanation="Storage source was not initialized; cannot initialize " +
+ "link discovery.",
+ recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG),
+ @LogMessageDoc(level="ERROR",
+ message="Exception in LLDP send timer.",
+ explanation="An unknown error occured while sending LLDP " +
+ "messages to switches.",
+ recommendation=LogMessageDoc.CHECK_SWITCH)
+ })
+ public void startUp(FloodlightModuleContext context) {
+ // Create our storage tables
+ if (storageSource == null) {
+ log.error("No storage source found.");
+ return;
+ }
+
+ storageSource.createTable(LINK_TABLE_NAME, null);
+ storageSource.setTablePrimaryKeyName(LINK_TABLE_NAME, LINK_ID);
+ storageSource.deleteMatchingRows(LINK_TABLE_NAME, null);
+ // Register for storage updates for the switch table
+ try {
+ storageSource.addListener(SWITCH_CONFIG_TABLE_NAME, this);
+ } catch (StorageException ex) {
+ log.error("Error in installing listener for " +
+ "switch table {}", SWITCH_CONFIG_TABLE_NAME);
+ }
+
+ ScheduledExecutorService ses = threadPool.getScheduledExecutor();
+
+ // To be started by the first switch connection
+ discoveryTask = new SingletonTask(ses, new Runnable() {
+ @Override
+ public void run() {
+ try {
+ discoverLinks();
+ } catch (StorageException e) {
+ log.error("Storage exception in LLDP send timer; " +
+ "terminating process", e);
+ floodlightProvider.terminate();
+ } catch (Exception e) {
+ log.error("Exception in LLDP send timer.", e);
+ } finally {
+ if (!shuttingDown) {
+ // null role implies HA mode is not enabled.
+ Role role = floodlightProvider.getRole();
+ if (role == null || role == Role.MASTER) {
+ log.trace("Rescheduling discovery task as role = {}", role);
+ discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL,
+ TimeUnit.SECONDS);
+ } else {
+ log.trace("Stopped LLDP rescheduling due to role = {}.", role);
+ }
+ }
+ }
+ }
+ });
+
+ // null role implies HA mode is not enabled.
+ Role role = floodlightProvider.getRole();
+ if (role == null || role == Role.MASTER) {
+ log.trace("Setup: Rescheduling discovery task. role = {}", role);
+ discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL, TimeUnit.SECONDS);
+ } else {
+ log.trace("Setup: Not scheduling LLDP as role = {}.", role);
+ }
+
+ // Setup the BDDP task. It is invoked whenever switch port tuples
+ // are added to the quarantine list.
+ bddpTask = new SingletonTask(ses, new QuarantineWorker());
+ bddpTask.reschedule(BDDP_TASK_INTERVAL, TimeUnit.MILLISECONDS);
+
+ updatesThread = new Thread(new Runnable () {
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ doUpdatesThread();
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+ }}, "Topology Updates");
+ updatesThread.start();
+
+
+
+ // Register for the OpenFlow messages we want to receive
+ floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+ floodlightProvider.addOFMessageListener(OFType.PORT_STATUS, this);
+ // Register for switch updates
+ floodlightProvider.addOFSwitchListener(this);
+ floodlightProvider.addHAListener(this);
+ floodlightProvider.addInfoProvider("summary", this);
+ if (restApi != null)
+ restApi.addRestletRoutable(new LinkDiscoveryWebRoutable());
+ setControllerTLV();
+ }
+
+ // ****************************************************
+ // Topology Manager's Event History members and methods
+ // ****************************************************
+
+ // Topology Manager event history
+ public EventHistory<EventHistoryTopologySwitch> evHistTopologySwitch;
+ public EventHistory<EventHistoryTopologyLink> evHistTopologyLink;
+ public EventHistory<EventHistoryTopologyCluster> evHistTopologyCluster;
+ public EventHistoryTopologySwitch evTopoSwitch;
+ public EventHistoryTopologyLink evTopoLink;
+ public EventHistoryTopologyCluster evTopoCluster;
+
+ // Switch Added/Deleted
+ private void evHistTopoSwitch(IOFSwitch sw, EvAction actn, String reason) {
+ if (evTopoSwitch == null) {
+ evTopoSwitch = new EventHistoryTopologySwitch();
+ }
+ evTopoSwitch.dpid = sw.getId();
+ if ((sw.getChannel() != null) &&
+ (SocketAddress.class.isInstance(
+ sw.getChannel().getRemoteAddress()))) {
+ evTopoSwitch.ipv4Addr =
+ IPv4.toIPv4Address(((InetSocketAddress)(sw.getChannel().
+ getRemoteAddress())).getAddress().getAddress());
+ evTopoSwitch.l4Port =
+ ((InetSocketAddress)(sw.getChannel().
+ getRemoteAddress())).getPort();
+ } else {
+ evTopoSwitch.ipv4Addr = 0;
+ evTopoSwitch.l4Port = 0;
+ }
+ evTopoSwitch.reason = reason;
+ evTopoSwitch = evHistTopologySwitch.put(evTopoSwitch, actn);
+ }
+
+ private void evHistTopoLink(long srcDpid, long dstDpid, short srcPort,
+ short dstPort, int srcPortState, int dstPortState,
+ ILinkDiscovery.LinkType linkType,
+ EvAction actn, String reason) {
+ if (evTopoLink == null) {
+ evTopoLink = new EventHistoryTopologyLink();
+ }
+ evTopoLink.srcSwDpid = srcDpid;
+ evTopoLink.dstSwDpid = dstDpid;
+ evTopoLink.srcSwport = srcPort & 0xffff;
+ evTopoLink.dstSwport = dstPort & 0xffff;
+ evTopoLink.srcPortState = srcPortState;
+ evTopoLink.dstPortState = dstPortState;
+ evTopoLink.reason = reason;
+ switch (linkType) {
+ case DIRECT_LINK:
+ evTopoLink.linkType = "DIRECT_LINK";
+ break;
+ case MULTIHOP_LINK:
+ evTopoLink.linkType = "MULTIHOP_LINK";
+ break;
+ case TUNNEL:
+ evTopoLink.linkType = "TUNNEL";
+ break;
+ case INVALID_LINK:
+ default:
+ evTopoLink.linkType = "Unknown";
+ break;
+ }
+ evTopoLink = evHistTopologyLink.put(evTopoLink, actn);
+ }
+
+ public void evHistTopoCluster(long dpid, long clusterIdOld,
+ long clusterIdNew, EvAction action, String reason) {
+ if (evTopoCluster == null) {
+ evTopoCluster = new EventHistoryTopologyCluster();
+ }
+ evTopoCluster.dpid = dpid;
+ evTopoCluster.clusterIdOld = clusterIdOld;
+ evTopoCluster.clusterIdNew = clusterIdNew;
+ evTopoCluster.reason = reason;
+ evTopoCluster = evHistTopologyCluster.put(evTopoCluster, action);
+ }
+
+ @Override
+ public Map<String, Object> getInfo(String type) {
+ if (!"summary".equals(type)) return null;
+
+ Map<String, Object> info = new HashMap<String, Object>();
+
+ int num_links = 0;
+ for (Set<Link> links : switchLinks.values())
+ num_links += links.size();
+ info.put("# inter-switch links", num_links / 2);
+
+ return info;
+ }
+
+ // IHARoleListener
+ @Override
+ public void roleChanged(Role oldRole, Role newRole) {
+ switch(newRole) {
+ case MASTER:
+ if (oldRole == Role.SLAVE) {
+ if (log.isTraceEnabled()) {
+ log.trace("Sending LLDPs " +
+ "to HA change from SLAVE->MASTER");
+ }
+ clearAllLinks();
+ log.debug("Role Change to Master: Rescheduling discovery task.");
+ discoveryTask.reschedule(1, TimeUnit.MICROSECONDS);
+ }
+ break;
+ case SLAVE:
+ if (log.isTraceEnabled()) {
+ log.trace("Clearing links due to " +
+ "HA change to SLAVE");
+ }
+ switchLinks.clear();
+ links.clear();
+ portLinks.clear();
+ portBroadcastDomainLinks.clear();
+ discoverOnAllPorts();
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void controllerNodeIPsChanged(
+ Map<String, String> curControllerNodeIPs,
+ Map<String, String> addedControllerNodeIPs,
+ Map<String, String> removedControllerNodeIPs) {
+ // ignore
+ }
+
+ public boolean isAutoPortFastFeature() {
+ return autoPortFastFeature;
+ }
+
+ public void setAutoPortFastFeature(boolean autoPortFastFeature) {
+ this.autoPortFastFeature = autoPortFastFeature;
+ }
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/AutoPortFast.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/AutoPortFast.java
new file mode 100644
index 0000000..29dc890
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/AutoPortFast.java
@@ -0,0 +1,31 @@
+package net.onrc.onos.ofcontroller.linkdiscovery.web;
+
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscoveryService;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AutoPortFast extends ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(AutoPortFast.class);
+
+ @Get("json")
+ public String retrieve() {
+ ILinkDiscoveryService linkDiscovery;
+ linkDiscovery = (ILinkDiscoveryService)getContext().getAttributes().
+ get(ILinkDiscoveryService.class.getCanonicalName());
+
+ String param = ((String)getRequestAttributes().get("state")).toLowerCase();
+ if (param.equals("enable") || param.equals("true")) {
+ linkDiscovery.setAutoPortFastFeature(true);
+ } else if (param.equals("disable") || param.equals("false")) {
+ linkDiscovery.setAutoPortFastFeature(false);
+ }
+ setStatus(Status.SUCCESS_OK, "OK");
+ if (linkDiscovery.isAutoPortFastFeature())
+ return "enabled";
+ else return "disabled";
+ }
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/LinkDiscoveryWebRoutable.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/LinkDiscoveryWebRoutable.java
new file mode 100644
index 0000000..8eae558
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/LinkDiscoveryWebRoutable.java
@@ -0,0 +1,26 @@
+package net.onrc.onos.ofcontroller.linkdiscovery.web;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.routing.Router;
+
+public class LinkDiscoveryWebRoutable implements RestletRoutable {
+ /**
+ * Create the Restlet router and bind to the proper resources.
+ */
+ @Override
+ public Router getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/autoportfast/{state}/json", AutoPortFast.class); // enable/true or disable/false
+ return router;
+ }
+
+ /**
+ * Set the base path for the Topology
+ */
+ @Override
+ public String basePath() {
+ return "/wm/linkdiscovery";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/LinkWithType.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/LinkWithType.java
new file mode 100644
index 0000000..3e5a5e7
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/LinkWithType.java
@@ -0,0 +1,65 @@
+package net.onrc.onos.ofcontroller.linkdiscovery.web;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+
+import net.floodlightcontroller.routing.Link;
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery.LinkType;
+
+/**
+ * This class is both the datastructure and the serializer
+ * for a link with the corresponding type of link.
+ * @author alexreimers
+ */
+@JsonSerialize(using=LinkWithType.class)
+public class LinkWithType extends JsonSerializer<LinkWithType> {
+ public long srcSwDpid;
+ public short srcPort;
+ public int srcPortState;
+ public long dstSwDpid;
+ public short dstPort;
+ public int dstPortState;
+ public LinkType type;
+
+ // Do NOT delete this, it's required for the serializer
+ public LinkWithType() {}
+
+ public LinkWithType(Link link,
+ int srcPortState,
+ int dstPortState,
+ LinkType type) {
+ this.srcSwDpid = link.getSrc();
+ this.srcPort = link.getSrcPort();
+ this.srcPortState = srcPortState;
+ this.dstSwDpid = link.getDst();
+ this.dstPort = link.getDstPort();
+ this.dstPortState = dstPortState;
+ this.type = type;
+ }
+
+ @Override
+ public void serialize(LinkWithType lwt, JsonGenerator jgen, SerializerProvider arg2)
+ throws IOException, JsonProcessingException {
+ // You ****MUST*** use lwt for the fields as it's actually a different object.
+ jgen.writeStartObject();
+ jgen.writeStringField("src-switch", HexString.toHexString(lwt.srcSwDpid));
+ jgen.writeNumberField("src-port", lwt.srcPort);
+ jgen.writeNumberField("src-port-state", lwt.srcPortState);
+ jgen.writeStringField("dst-switch", HexString.toHexString(lwt.dstSwDpid));
+ jgen.writeNumberField("dst-port", lwt.dstPort);
+ jgen.writeNumberField("dst-port-state", lwt.dstPortState);
+ jgen.writeStringField("type", lwt.type.toString());
+ jgen.writeEndObject();
+ }
+
+ @Override
+ public Class<LinkWithType> handledType() {
+ return LinkWithType.class;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/LinksResource.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/LinksResource.java
new file mode 100644
index 0000000..c522a05
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/LinksResource.java
@@ -0,0 +1,37 @@
+package net.onrc.onos.ofcontroller.linkdiscovery.web;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.routing.Link;
+import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.onrc.onos.ofcontroller.linkdiscovery.LinkInfo;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class LinksResource extends ServerResource {
+
+ @Get("json")
+ public Set<LinkWithType> retrieve() {
+ ILinkDiscoveryService ld = (ILinkDiscoveryService)getContext().getAttributes().
+ get(ILinkDiscoveryService.class.getCanonicalName());
+ Map<Link, LinkInfo> links = new HashMap<Link, LinkInfo>();
+ Set<LinkWithType> returnLinkSet = new HashSet<LinkWithType>();
+
+ if (ld != null) {
+ links.putAll(ld.getLinks());
+ for (Link link: links.keySet()) {
+ LinkInfo info = links.get(link);
+ LinkWithType lwt = new LinkWithType(link,
+ info.getSrcPortState(),
+ info.getDstPortState(),
+ ld.getLinkType(link, info));
+ returnLinkSet.add(lwt);
+ }
+ }
+ return returnLinkSet;
+ }
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/TopoLinksResource.java b/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/TopoLinksResource.java
deleted file mode 100644
index b48ebde..0000000
--- a/src/main/java/net/onrc/onos/ofcontroller/linkdiscovery/web/TopoLinksResource.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package net.onrc.onos.ofcontroller.linkdiscovery.web;
-
-import java.util.List;
-import net.floodlightcontroller.routing.Link;
-import net.onrc.onos.ofcontroller.core.internal.TopoLinkServiceImpl;
-
-import org.restlet.resource.Get;
-import org.restlet.resource.ServerResource;
-
-public class TopoLinksResource extends ServerResource {
-
- @Get("json")
- public List<Link> retrieve() {
- TopoLinkServiceImpl impl = new TopoLinkServiceImpl();
-
- List<Link> retval = impl.getActiveLinks();
- return retval;
- }
-
-}