blob: 9092e98cf54ccfe538065b0d76fe3523e1e7d66b [file] [log] [blame]
package net.onrc.onos.core.topology;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import javax.annotation.concurrent.GuardedBy;
import net.floodlightcontroller.util.MACAddress;
import net.onrc.onos.core.datagrid.IDatagridService;
import net.onrc.onos.core.datagrid.IEventChannel;
import net.onrc.onos.core.datagrid.IEventChannelListener;
import net.onrc.onos.core.metrics.OnosMetrics;
import net.onrc.onos.core.metrics.OnosMetrics.MetricsComponent;
import net.onrc.onos.core.metrics.OnosMetrics.MetricsFeature;
import net.onrc.onos.core.registry.IControllerRegistryService;
import net.onrc.onos.core.util.Dpid;
import net.onrc.onos.core.util.EventEntry;
import net.onrc.onos.core.util.SwitchPort;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
/**
* The TopologyManager receives topology updates from the southbound discovery
* modules and from other ONOS instances. These updates are processed and
* applied to the in-memory topology instance.
* <p/>
* - Maintain Invariant/Relationships between Topology Objects.
* <p/>
* TODO To be synchronized based on TopologyEvent Notification.
* <p/>
* TODO TBD: Caller is expected to maintain parent/child calling order. Parent
* Object must exist before adding sub component(Add Switch -> Port).
* <p/>
* TODO TBD: This class may delay the requested change to handle event
* re-ordering. e.g.) Link Add came in, but Switch was not there.
*/
public class TopologyManager {
private static final Logger log = LoggerFactory
.getLogger(TopologyManager.class);
private IEventChannel<byte[], TopologyEvent> eventChannel;
public static final String EVENT_CHANNEL_NAME = "onos.topology";
private EventHandler eventHandler = new EventHandler();
private final TopologyImpl topology = new TopologyImpl();
private TopologyEventPreprocessor eventPreprocessor;
private CopyOnWriteArrayList<ITopologyListener> topologyListeners =
new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<ITopologyListener> newTopologyListeners =
new CopyOnWriteArrayList<>();
//
// Metrics
//
private static final MetricsComponent METRICS_COMPONENT =
OnosMetrics.registerComponent("Topology");
private static final MetricsFeature METRICS_FEATURE_EVENT_NOTIFICATION =
METRICS_COMPONENT.registerFeature("EventNotification");
//
// Timestamp of the last Topology event (ms from the Epoch)
private volatile long lastEventTimestampEpochMs = 0;
private final Gauge<Long> gaugeLastEventTimestampEpochMs =
OnosMetrics.registerMetric(METRICS_COMPONENT,
METRICS_FEATURE_EVENT_NOTIFICATION,
"LastEventTimestamp.EpochMs",
new Gauge<Long>() {
@Override
public Long getValue() {
return lastEventTimestampEpochMs;
}
});
// Rate of the Topology events published to the Topology listeners
private final Meter listenerEventRate =
OnosMetrics.createMeter(METRICS_COMPONENT,
METRICS_FEATURE_EVENT_NOTIFICATION,
"ListenerEventRate");
//
// Local state for keeping the last ADD Mastership Event entries.
// TODO: In the future, we might have to keep this state somewhere else.
//
private Map<ByteBuffer, MastershipData> lastAddMastershipDataEntries =
new HashMap<>();
//
// Local state for keeping track of the application event notifications
//
// - Queue of events, which will be dispatched to local listeners
// on next notification.
private List<MastershipData> apiAddedMastershipDataEntries =
new LinkedList<>();
private List<MastershipData> apiRemovedMastershipDataEntries =
new LinkedList<>();
private List<SwitchData> apiAddedSwitchDataEntries = new LinkedList<>();
private List<SwitchData> apiRemovedSwitchDataEntries = new LinkedList<>();
private List<PortData> apiAddedPortDataEntries = new LinkedList<>();
private List<PortData> apiRemovedPortDataEntries = new LinkedList<>();
private List<LinkData> apiAddedLinkDataEntries = new LinkedList<>();
private List<LinkData> apiRemovedLinkDataEntries = new LinkedList<>();
private List<HostData> apiAddedHostDataEntries = new LinkedList<>();
private List<HostData> apiRemovedHostDataEntries = new LinkedList<>();
/**
* Constructor.
*
* @param registryService the Registry Service to use.
*/
public TopologyManager(IControllerRegistryService registryService) {
this.eventPreprocessor =
new TopologyEventPreprocessor(registryService);
}
/**
* Get the MutableTopology.
*
* @return the MutableTopology.
*/
MutableTopology getTopology() {
return topology;
}
/**
* Event handler class.
*/
class EventHandler extends Thread implements
IEventChannelListener<byte[], TopologyEvent> {
private BlockingQueue<EventEntry<TopologyEvent>> topologyEvents =
new LinkedBlockingQueue<EventEntry<TopologyEvent>>();
/**
* Startup processing.
*/
private void startup() {
//
// Read all topology state
//
Collection<TopologyEvent> allTopologyEvents =
eventChannel.getAllEntries();
List<EventEntry<TopologyEvent>> events =
new LinkedList<EventEntry<TopologyEvent>>();
for (TopologyEvent topologyEvent : allTopologyEvents) {
EventEntry<TopologyEvent> eventEntry =
new EventEntry<TopologyEvent>(EventEntry.Type.ENTRY_ADD,
topologyEvent);
events.add(eventEntry);
}
processEvents(events);
}
/**
* Run the thread.
*/
@Override
public void run() {
List<EventEntry<TopologyEvent>> events =
new LinkedList<EventEntry<TopologyEvent>>();
this.setName("TopologyManager.EventHandler " + this.getId());
startup();
//
// The main loop
//
while (true) {
try {
EventEntry<TopologyEvent> eventEntry =
topologyEvents.take();
events.add(eventEntry);
topologyEvents.drainTo(events);
processEvents(events);
events.clear();
} catch (Exception exception) {
log.debug("Exception processing Topology Events: ",
exception);
}
}
}
/**
* Process all topology events.
*
* @param events the events to process.
*/
private void processEvents(List<EventEntry<TopologyEvent>> events) {
//
// Process pending (new) listeners
//
processPendingListeners();
//
// Pre-process the events
//
events = eventPreprocessor.processEvents(events);
//
// Lock the topology while it is modified
//
topology.acquireWriteLock();
try {
// Apply the events
//
// NOTE: The events are suppose to be in the proper order
// to naturally build and update the topology.
//
for (EventEntry<TopologyEvent> event : events) {
// Ignore NO-OP events
if (event.isNoop()) {
continue;
}
TopologyEvent topologyEvent = event.eventData();
// Get the event itself
MastershipData mastershipData =
topologyEvent.getMastershipData();
SwitchData switchData = topologyEvent.getSwitchData();
PortData portData = topologyEvent.getPortData();
LinkData linkData = topologyEvent.getLinkData();
HostData hostData = topologyEvent.getHostData();
boolean wasAdded = false;
//
// Extract the events
//
switch (event.eventType()) {
case ENTRY_ADD:
if (mastershipData != null) {
wasAdded = addMastershipData(mastershipData);
}
if (switchData != null) {
wasAdded = addSwitch(switchData);
}
if (portData != null) {
wasAdded = addPort(portData);
}
if (linkData != null) {
wasAdded = addLink(linkData);
}
if (hostData != null) {
wasAdded = addHost(hostData);
}
// If the item wasn't added, probably it was reordered
if (!wasAdded) {
ByteBuffer id = topologyEvent.getIDasByteBuffer();
eventPreprocessor.reorderedEvents.put(id, topologyEvent);
}
break;
case ENTRY_REMOVE:
if (mastershipData != null) {
removeMastershipData(mastershipData);
}
if (switchData != null) {
removeSwitch(switchData);
}
if (portData != null) {
removePort(portData);
}
if (linkData != null) {
removeLink(linkData);
}
if (hostData != null) {
removeHost(hostData);
}
break;
default:
log.error("Unknown topology event {}",
event.eventType());
}
}
} finally {
//
// Topology modifications completed: Release the lock
//
topology.releaseWriteLock();
}
//
// Dispatch the Topology Notification Events to the applications
//
dispatchTopologyEvents();
}
/**
* Receive a notification that an entry is added.
*
* @param value the value for the entry.
*/
@Override
public void entryAdded(TopologyEvent value) {
EventEntry<TopologyEvent> eventEntry =
new EventEntry<TopologyEvent>(EventEntry.Type.ENTRY_ADD,
value);
topologyEvents.add(eventEntry);
}
/**
* Receive a notification that an entry is removed.
*
* @param value the value for the entry.
*/
@Override
public void entryRemoved(TopologyEvent value) {
EventEntry<TopologyEvent> eventEntry =
new EventEntry<TopologyEvent>(EventEntry.Type.ENTRY_REMOVE,
value);
topologyEvents.add(eventEntry);
}
/**
* Receive a notification that an entry is updated.
*
* @param value the value for the entry.
*/
@Override
public void entryUpdated(TopologyEvent value) {
// NOTE: The ADD and UPDATE events are processed in same way
entryAdded(value);
}
/**
* Informs the event handler that a new listener has been added,
* and that listener expects the first event to be a snapshot of the
* current topology.
*/
void listenerAdded() {
//
// Generate a NO-OP event so the Event Handler processing can be
// triggered to generate in-order a snapshot of the current
// topology.
// TODO: This is a hack.
//
EventEntry<TopologyEvent> eventEntry = EventEntry.makeNoop();
topologyEvents.add(eventEntry);
}
}
/**
* Startup processing.
*
* @param datagridService the datagrid service to use.
*/
void startup(IDatagridService datagridService) {
eventChannel = datagridService.addListener(EVENT_CHANNEL_NAME,
eventHandler,
byte[].class,
TopologyEvent.class);
eventHandler.start();
}
/**
* Adds a listener for topology events.
*
* @param listener the listener to add.
* @param startFromSnapshot if true, and if the topology is not
* empty, the first event should be a snapshot of the current topology.
*/
void addListener(ITopologyListener listener, boolean startFromSnapshot) {
if (startFromSnapshot) {
newTopologyListeners.addIfAbsent(listener);
eventHandler.listenerAdded();
} else {
topologyListeners.addIfAbsent(listener);
}
}
/**
* Removes a listener for topology events. The listener will no longer
* receive topology events after this call.
*
* @param listener the listener to remove.
*/
void removeListener(ITopologyListener listener) {
topologyListeners.remove(listener);
newTopologyListeners.remove(listener);
}
/**
* Processes pending (new) listeners.
* <p>
* During the processing, we dispatch Topology Snapshot Events to new
* listeners.
*/
private void processPendingListeners() {
if (newTopologyListeners.isEmpty()) {
return;
}
//
// Create the Topology Snapshot Event
//
TopologyEvents events = null;
Collection<MastershipData> mastershipDataEntries =
lastAddMastershipDataEntries.values();
Collection<SwitchData> switchDataEntries = topology.getAllSwitchDataEntries();
Collection<PortData> portDataEntries = topology.getAllPortDataEntries();
Collection<LinkData> linkDataEntries = topology.getAllLinkDataEntries();
Collection<HostData> hostDataEntries = topology.getAllHostDataEntries();
if (!(mastershipDataEntries.isEmpty() &&
switchDataEntries.isEmpty() &&
portDataEntries.isEmpty() &&
linkDataEntries.isEmpty() &&
hostDataEntries.isEmpty())) {
events = new TopologyEvents(mastershipDataEntries,
switchDataEntries,
portDataEntries,
linkDataEntries,
hostDataEntries);
}
//
// Dispatch Snapshot Event to each new listener, and keep track
// of each processed listener.
//
// NOTE: We deliver the event only if it is not empty.
// NOTE: We need to execute the loop so we can properly
// move the new listeners together with the older listeners.
//
List<ITopologyListener> processedListeners = new LinkedList<>();
for (ITopologyListener listener : newTopologyListeners) {
processedListeners.add(listener);
// Move the new listener together with the rest of the listeners
topologyListeners.addIfAbsent(listener);
// Dispatch the event
if (events != null) {
listener.topologyEvents(events);
}
}
newTopologyListeners.removeAll(processedListeners);
}
/**
* Dispatch Topology Events to the listeners.
*/
private void dispatchTopologyEvents() {
if (apiAddedMastershipDataEntries.isEmpty() &&
apiRemovedMastershipDataEntries.isEmpty() &&
apiAddedSwitchDataEntries.isEmpty() &&
apiRemovedSwitchDataEntries.isEmpty() &&
apiAddedPortDataEntries.isEmpty() &&
apiRemovedPortDataEntries.isEmpty() &&
apiAddedLinkDataEntries.isEmpty() &&
apiRemovedLinkDataEntries.isEmpty() &&
apiAddedHostDataEntries.isEmpty() &&
apiRemovedHostDataEntries.isEmpty()) {
return; // No events to dispatch
}
if (log.isDebugEnabled()) {
//
// Debug statements
// TODO: Those statements should be removed in the future
//
for (MastershipData mastershipData : apiAddedMastershipDataEntries) {
log.debug("Dispatch Topology Event: ADDED {}",
mastershipData);
}
for (MastershipData mastershipData : apiRemovedMastershipDataEntries) {
log.debug("Dispatch Topology Event: REMOVED {}",
mastershipData);
}
for (SwitchData switchData : apiAddedSwitchDataEntries) {
log.debug("Dispatch Topology Event: ADDED {}", switchData);
}
for (SwitchData switchData : apiRemovedSwitchDataEntries) {
log.debug("Dispatch Topology Event: REMOVED {}", switchData);
}
for (PortData portData : apiAddedPortDataEntries) {
log.debug("Dispatch Topology Event: ADDED {}", portData);
}
for (PortData portData : apiRemovedPortDataEntries) {
log.debug("Dispatch Topology Event: REMOVED {}", portData);
}
for (LinkData linkData : apiAddedLinkDataEntries) {
log.debug("Dispatch Topology Event: ADDED {}", linkData);
}
for (LinkData linkData : apiRemovedLinkDataEntries) {
log.debug("Dispatch Topology Event: REMOVED {}", linkData);
}
for (HostData hostData : apiAddedHostDataEntries) {
log.debug("Dispatch Topology Event: ADDED {}", hostData);
}
for (HostData hostData : apiRemovedHostDataEntries) {
log.debug("Dispatch Topology Event: REMOVED {}", hostData);
}
}
//
// Update the metrics
//
long totalEvents =
apiAddedMastershipDataEntries.size() + apiRemovedMastershipDataEntries.size() +
apiAddedSwitchDataEntries.size() + apiRemovedSwitchDataEntries.size() +
apiAddedPortDataEntries.size() + apiRemovedPortDataEntries.size() +
apiAddedLinkDataEntries.size() + apiRemovedLinkDataEntries.size() +
apiAddedHostDataEntries.size() + apiRemovedHostDataEntries.size();
this.listenerEventRate.mark(totalEvents);
this.lastEventTimestampEpochMs = System.currentTimeMillis();
//
// Allocate the events to deliver.
//
TopologyEvents events = new TopologyEvents(
apiAddedMastershipDataEntries,
apiRemovedMastershipDataEntries,
apiAddedSwitchDataEntries,
apiRemovedSwitchDataEntries,
apiAddedPortDataEntries,
apiRemovedPortDataEntries,
apiAddedLinkDataEntries,
apiRemovedLinkDataEntries,
apiAddedHostDataEntries,
apiRemovedHostDataEntries);
//
// Deliver the events
//
for (ITopologyListener listener : this.topologyListeners) {
listener.topologyEvents(events);
}
//
// Cleanup
//
apiAddedMastershipDataEntries.clear();
apiRemovedMastershipDataEntries.clear();
apiAddedSwitchDataEntries.clear();
apiRemovedSwitchDataEntries.clear();
apiAddedPortDataEntries.clear();
apiRemovedPortDataEntries.clear();
apiAddedLinkDataEntries.clear();
apiRemovedLinkDataEntries.clear();
apiAddedHostDataEntries.clear();
apiRemovedHostDataEntries.clear();
}
//
// Methods to update topology replica
//
/**
* Adds Switch Mastership event.
*
* @param mastershipData the MastershipData to process.
* @return true if the item was successfully added, otherwise false.
*/
@GuardedBy("topology.writeLock")
private boolean addMastershipData(MastershipData mastershipData) {
log.debug("Added Mastership event {}", mastershipData);
lastAddMastershipDataEntries.put(mastershipData.getIDasByteBuffer(),
mastershipData);
apiAddedMastershipDataEntries.add(mastershipData);
return true;
}
/**
* Removes Switch Mastership event.
*
* @param mastershipData the MastershipData to process.
*/
@GuardedBy("topology.writeLock")
private void removeMastershipData(MastershipData mastershipData) {
log.debug("Removed Mastership event {}", mastershipData);
lastAddMastershipDataEntries.remove(mastershipData.getIDasByteBuffer());
apiRemovedMastershipDataEntries.add(mastershipData);
}
/**
* Adds a switch to the topology replica.
*
* @param switchData the SwitchData with the switch to add.
* @return true if the item was successfully added, otherwise false.
*/
@GuardedBy("topology.writeLock")
private boolean addSwitch(SwitchData switchData) {
if (log.isDebugEnabled()) {
SwitchData sw = topology.getSwitchData(switchData.getDpid());
if (sw != null) {
log.debug("Update {}", switchData);
} else {
log.debug("Added {}", switchData);
}
}
topology.putSwitch(switchData.freeze());
apiAddedSwitchDataEntries.add(switchData);
return true;
}
/**
* Removes a switch from the topology replica.
* <p/>
* It will call {@link #removePort(PortData)} for each ports on this
* switch.
*
* @param switchData the SwitchData with the switch to remove.
*/
@GuardedBy("topology.writeLock")
private void removeSwitch(SwitchData switchData) {
final Dpid dpid = switchData.getDpid();
SwitchData swInTopo = topology.getSwitchData(dpid);
if (swInTopo == null) {
log.warn("Switch {} already removed, ignoring", switchData);
return;
}
//
// Remove all Ports on the Switch
//
ArrayList<PortData> portsToRemove = new ArrayList<>();
for (Port port : topology.getPorts(dpid)) {
log.warn("Port {} on Switch {} should be removed prior to removing Switch. Removing Port now.",
port, switchData);
PortData portData = new PortData(port.getSwitchPort());
portsToRemove.add(portData);
}
for (PortData portData : portsToRemove) {
removePort(portData);
}
log.debug("Removed {}", swInTopo);
topology.removeSwitch(dpid);
apiRemovedSwitchDataEntries.add(swInTopo);
}
/**
* Adds a port to the topology replica.
*
* @param portData the PortData with the port to add.
* @return true if the item was successfully added, otherwise false.
*/
@GuardedBy("topology.writeLock")
private boolean addPort(PortData portData) {
Switch sw = topology.getSwitch(portData.getDpid());
if (sw == null) {
// Reordered event
log.debug("{} reordered because switch is null", portData);
return false;
}
if (log.isDebugEnabled()) {
PortData port = topology.getPortData(portData.getSwitchPort());
if (port != null) {
log.debug("Update {}", portData);
} else {
log.debug("Added {}", portData);
}
}
topology.putPort(portData.freeze());
apiAddedPortDataEntries.add(portData);
return true;
}
/**
* Removes a port from the topology replica.
* <p/>
* It will remove attachment points from each hosts on this port
* and call {@link #removeLink(LinkData)} for each links on this port.
*
* @param portData the PortData with the port to remove.
*/
@GuardedBy("topology.writeLock")
private void removePort(PortData portData) {
SwitchData sw = topology.getSwitchData(portData.getDpid());
if (sw == null) {
log.warn("Parent Switch for Port {} already removed, ignoring",
portData);
return;
}
final SwitchPort switchPort = portData.getSwitchPort();
PortData portInTopo = topology.getPortData(switchPort);
if (portInTopo == null) {
log.warn("Port {} already removed, ignoring", portData);
return;
}
//
// Remove all Host attachment points bound to this Port
//
List<HostData> hostsToUpdate = new ArrayList<>();
for (Host host : topology.getHosts(switchPort)) {
log.debug("Removing Host {} on Port {}", host, portInTopo);
HostData hostData = topology.getHostData(host.getMacAddress());
hostsToUpdate.add(hostData);
}
for (HostData hostData : hostsToUpdate) {
HostData newHostData = new HostData(hostData);
newHostData.removeAttachmentPoint(switchPort);
newHostData.freeze();
// TODO should this event be fired inside #addHost?
if (newHostData.getAttachmentPoints().isEmpty()) {
// No more attachment point left -> remove Host
removeHost(hostData);
} else {
// Update Host
addHost(newHostData);
}
}
//
// Remove all Links connected to the Port
//
Set<Link> links = new HashSet<>();
links.addAll(topology.getOutgoingLinks(switchPort));
links.addAll(topology.getIncomingLinks(switchPort));
for (Link link : links) {
if (link == null) {
continue;
}
LinkData linkData = topology.getLinkData(link.getLinkTuple());
if (linkData != null) {
log.debug("Removing Link {} on Port {}", link, portInTopo);
removeLink(linkData);
}
}
// Remove the Port from Topology
log.debug("Removed {}", portInTopo);
topology.removePort(switchPort);
apiRemovedPortDataEntries.add(portInTopo);
}
/**
* Adds a link to the topology replica.
* <p/>
* It will remove attachment points from each hosts using the same ports.
*
* @param linkData the LinkData with the link to add.
* @return true if the item was successfully added, otherwise false.
*/
@GuardedBy("topology.writeLock")
private boolean addLink(LinkData linkData) {
PortData srcPort = topology.getPortData(linkData.getSrc());
PortData dstPort = topology.getPortData(linkData.getDst());
if ((srcPort == null) || (dstPort == null)) {
// Reordered event
log.debug("{} reordered because {} port is null", linkData,
(srcPort == null) ? "src" : "dst");
return false;
}
//
// XXX domain knowledge: Sanity check: Port cannot have both Link and
// Host.
//
// FIXME: Potentially local replica may not be up-to-date yet due to
// Hazelcast delay.
// FIXME: May need to manage local truth and use them instead.
//
if (topology.getLinkData(linkData.getLinkTuple()) == null) {
// Only check for existing Host when adding new Link.
// Remove all Hosts attached to the ports on both ends
Set<HostData> hostsToUpdate =
new TreeSet<>(new Comparator<HostData>() {
// Comparison only using ID(=MAC)
@Override
public int compare(HostData o1, HostData o2) {
return Long.compare(o1.getMac().toLong(), o2.getMac().toLong());
}
});
List<SwitchPort> portsToCheck = Arrays.asList(
srcPort.getSwitchPort(),
dstPort.getSwitchPort());
// Enumerate Host which needs to be updated by this Link add event
for (SwitchPort port : portsToCheck) {
for (Host host : topology.getHosts(port)) {
log.error("Host {} on Port {} should have been removed prior to adding Link {}",
host, port, linkData);
HostData hostData =
topology.getHostData(host.getMacAddress());
hostsToUpdate.add(hostData);
}
}
// Remove attachment point from them
for (HostData hostData : hostsToUpdate) {
// Remove port from attachment point and update
HostData newHostData = new HostData(hostData);
newHostData.removeAttachmentPoint(srcPort.getSwitchPort());
newHostData.removeAttachmentPoint(dstPort.getSwitchPort());
newHostData.freeze();
// TODO should this event be fired inside #addHost?
if (newHostData.getAttachmentPoints().isEmpty()) {
// No more attachment point left -> remove Host
removeHost(hostData);
} else {
// Update Host
addHost(newHostData);
}
}
}
if (log.isDebugEnabled()) {
LinkData link = topology.getLinkData(linkData.getLinkTuple());
if (link != null) {
log.debug("Update {}", linkData);
} else {
log.debug("Added {}", linkData);
}
}
topology.putLink(linkData.freeze());
apiAddedLinkDataEntries.add(linkData);
return true;
}
/**
* Removes a link from the topology replica.
*
* @param linkData the LinkData with the link to remove.
*/
@GuardedBy("topology.writeLock")
private void removeLink(LinkData linkData) {
Port srcPort = topology.getPort(linkData.getSrc().getDpid(),
linkData.getSrc().getPortNumber());
if (srcPort == null) {
log.warn("Src Port for Link {} already removed, ignoring",
linkData);
return;
}
Port dstPort = topology.getPort(linkData.getDst().getDpid(),
linkData.getDst().getPortNumber());
if (dstPort == null) {
log.warn("Dst Port for Link {} already removed, ignoring",
linkData);
return;
}
LinkData linkInTopo = topology.getLinkData(linkData.getLinkTuple(),
linkData.getType());
if (linkInTopo == null) {
log.warn("Link {} already removed, ignoring", linkData);
return;
}
if (log.isDebugEnabled()) {
// only do sanity check on debug level
Link linkIn = dstPort.getIncomingLink(linkData.getType());
if (linkIn == null) {
log.warn("Link {} already removed on destination Port",
linkData);
}
Link linkOut = srcPort.getOutgoingLink(linkData.getType());
if (linkOut == null) {
log.warn("Link {} already removed on src Port", linkData);
}
}
log.debug("Removed {}", linkInTopo);
topology.removeLink(linkData.getLinkTuple(), linkData.getType());
apiRemovedLinkDataEntries.add(linkInTopo);
}
/**
* Adds a host to the topology replica.
* <p/>
* TODO: Host-related work is incomplete.
* TODO: Eventually, we might need to consider reordering
* or {@link #addLink(LinkData)} and {@link #addHost(HostData)} events
* on the same port.
*
* @param hostData the HostData with the host to add.
* @return true if the item was successfully added, otherwise false.
*/
@GuardedBy("topology.writeLock")
private boolean addHost(HostData hostData) {
// TODO Decide how to handle update scenario.
// If the new HostData has less attachment point compared to
// existing HostData, what should the event be?
// - Add HostData with some attachment point removed? (current behavior)
// create unfrozen copy
// for removing attachment points which already has a link
HostData modifiedHostData = new HostData(hostData);
// Verify each attachment point
boolean attachmentFound = false;
for (SwitchPort swp : hostData.getAttachmentPoints()) {
// XXX domain knowledge: Port must exist before Host
// but this knowledge cannot be pushed down to driver.
// Attached Ports must exist
Port port = topology.getPort(swp.getDpid(), swp.getPortNumber());
if (port == null) {
log.debug("{} reordered because port {} was not there",
hostData, swp);
// Reordered event
return false; // should not continue if re-applying later
}
// Attached Ports must not have Link
if (port.getOutgoingLink() != null ||
port.getIncomingLink() != null) {
log.warn("Link (Out:{},In:{}) exist on the attachment point. "
+ "Ignoring this attachmentpoint ({}) from {}.",
port.getOutgoingLink(), port.getIncomingLink(),
swp, modifiedHostData);
// FIXME Should either reject, reorder this HostData,
// or remove attachment point from given HostData
// Removing attachment point from given HostData for now.
modifiedHostData.removeAttachmentPoint(swp);
continue;
}
attachmentFound = true;
}
// Update the host in the topology
if (attachmentFound) {
if (modifiedHostData.getAttachmentPoints().isEmpty()) {
log.warn("No valid attachment point left. Ignoring."
+ "original: {}, modified: {}",
hostData, modifiedHostData);
// TODO Should we call #removeHost to trigger remove event?
// only if this call is update.
return false;
}
if (log.isDebugEnabled()) {
HostData host = topology.getHostData(hostData.getMac());
if (host != null) {
log.debug("Update {}", modifiedHostData);
} else {
log.debug("Added {}", modifiedHostData);
}
}
topology.putHost(modifiedHostData.freeze());
apiAddedHostDataEntries.add(modifiedHostData);
return true;
}
return false;
}
/**
* Removes a host from the topology replica.
* <p/>
* TODO: Host-related work is incomplete.
*
* @param hostData the Host Event with the host to remove.
*/
@GuardedBy("topology.writeLock")
private void removeHost(HostData hostData) {
final MACAddress mac = hostData.getMac();
HostData hostInTopo = topology.getHostData(mac);
if (hostInTopo == null) {
log.warn("Host {} already removed, ignoring", hostData);
return;
}
log.debug("Removed {}", hostInTopo);
topology.removeHost(mac);
apiRemovedHostDataEntries.add(hostInTopo);
}
}