blob: ebdde8f55484d047efac4bab2a7424bc1bd9a227 [file] [log] [blame]
package net.onrc.onos.core.topology;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import net.floodlightcontroller.core.IFloodlightProviderService.Role;
import net.onrc.onos.core.registry.IControllerRegistryService;
import net.onrc.onos.core.registry.RegistryException;
import net.onrc.onos.core.util.Dpid;
import net.onrc.onos.core.util.EventEntry;
import net.onrc.onos.core.util.OnosInstanceId;
import static com.google.common.base.Preconditions.checkNotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Topology Event pre-processor. It is used by the Topology Manager for
* pre-processing Topology events before applying them to the Topology.
* <p/>
* The pre-processor itself keeps internal state about the most recent
* ADD events. It also might keep state about reordered events that cannot
* be applied.
* <p/>
* As part of the pre-processing logic, a previously suppressed event might
* be genenerated later because of some other event.
*/
public class TopologyEventPreprocessor {
private static final Logger log = LoggerFactory
.getLogger(TopologyEventPreprocessor.class);
private final IControllerRegistryService registryService;
//
// Reordered ADD events that need to be reapplied
//
// TODO: For now, this field is accessed by the TopologyManager as well
// This should be refactored, and change them to private.
//
Map<ByteBuffer, TopologyEvent> reorderedEvents = new HashMap<>();
//
// Topology ADD event state per ONOS instance
//
private Map<OnosInstanceId, OnosInstanceLastAddEvents> instanceState =
new HashMap<>();
//
// Switch mastership state (updated by the topology events)
//
private Map<Dpid, OnosInstanceId> switchMastership = new HashMap<>();
/**
* Constructor for a given Registry Service.
*
* @param registryService the Registry Service to use.
*/
TopologyEventPreprocessor(IControllerRegistryService registryService) {
this.registryService = registryService;
}
/**
* Class to store the last ADD Topology Events per ONOS Instance.
*/
private final class OnosInstanceLastAddEvents {
private final OnosInstanceId onosInstanceId;
// The last ADD events received from this ONOS instance
private Map<ByteBuffer, TopologyEvent> topologyEvents = new HashMap<>();
/**
* Constructor for a given ONOS Instance ID.
*
* @param onosInstanceId the ONOS Instance ID.
*/
private OnosInstanceLastAddEvents(OnosInstanceId onosInstanceId) {
this.onosInstanceId = checkNotNull(onosInstanceId);
}
/**
* Processes an event originated by this ONOS instance.
*
* @param event the event to process.
* @return true if the event should be applied to the final Topology
* as well, otherwise false.
*/
private boolean processEvent(EventEntry<TopologyEvent> event) {
TopologyEvent topologyEvent = event.eventData();
ByteBuffer id = topologyEvent.getIDasByteBuffer();
OnosInstanceId masterId = null;
boolean isConfigured = false;
// Get the Master of the Origin DPID
Dpid dpid = topologyEvent.getOriginDpid();
if (dpid != null) {
masterId = switchMastership.get(dpid);
}
if (topologyEvent.getConfigState() == ConfigState.CONFIGURED) {
isConfigured = true;
}
//
// Apply the event based on its type
//
switch (event.eventType()) {
case ENTRY_ADD:
topologyEvents.put(id, topologyEvent);
reorderedEvents.remove(id);
// Allow the ADD only if the event was originated by the Master
return isConfigured || onosInstanceId.equals(masterId);
case ENTRY_REMOVE:
reorderedEvents.remove(id);
// Don't allow the REMOVE event if there was no ADD before
if (topologyEvents.remove(id) == null) {
return false;
}
//
// Allow the REMOVE if the event was originated by the Master,
// or there is no Master at all.
//
if (masterId == null) {
return true;
}
return isConfigured || onosInstanceId.equals(masterId);
default:
log.error("Unknown topology event {}", event.eventType());
}
return false;
}
/**
* Gets the postponed events for a given DPID.
* Those are the events that couldn't be applied earlier to the
* Topology, because the ONOS Instance originating the events
* was not the Master for the Switch.
*
* @param dpid the DPID to use.
* @return a list of postponed events for the given DPID.
*/
private List<EventEntry<TopologyEvent>> getPostponedEvents(Dpid dpid) {
List<EventEntry<TopologyEvent>> result = new LinkedList<>();
//
// Search all events, and keep only those that match the DPID
//
// TODO: This could be slow, and the code should be optimized
// for speed. The processing complexity is O(N*N) where N is
// the number of Switches: for each Switch Mastership we call
// getPostponedEvents(), and then for each call we
// search all previously added events.
// The code can be optimized by adding additional lookup map:
// Dpid -> List<TopologyEvent>
//
for (TopologyEvent te : topologyEvents.values()) {
if (dpid.equals(te.getOriginDpid())) {
result.add(new EventEntry<TopologyEvent>(EventEntry.Type.ENTRY_ADD, te));
}
}
return result;
}
}
/**
* Extracts previously reordered events that should be applied again
* to the Topology.
*
* @return a list of previously reordered events.
*/
private List<EventEntry<TopologyEvent>> extractReorderedEvents() {
List<EventEntry<TopologyEvent>> result = new LinkedList<>();
//
// Search all previously reordered events, and extract only if
// the originator is the Master.
//
List<TopologyEvent> leftoverEvents = new LinkedList<>();
for (TopologyEvent te : reorderedEvents.values()) {
Dpid dpid = te.getOriginDpid();
OnosInstanceId masterId = null;
if (dpid != null) {
masterId = switchMastership.get(dpid);
}
if (te.getOnosInstanceId().equals(masterId)) {
result.add(new EventEntry<TopologyEvent>(EventEntry.Type.ENTRY_ADD, te));
} else {
leftoverEvents.add(te);
}
}
//
// Add back the leftover events
//
reorderedEvents.clear();
for (TopologyEvent te : leftoverEvents) {
reorderedEvents.put(te.getIDasByteBuffer(), te);
}
return result;
}
/**
* Processes a Mastership Event.
*
* @param instance the ONOS instance state to use.
* @param event the event to process.
* @return a list of postponed events if the processed event is a
* Mastership Event for a new Master, otherwise an empty list.
*/
private List<EventEntry<TopologyEvent>> processMastershipData(
OnosInstanceLastAddEvents instance,
EventEntry<TopologyEvent> event) {
TopologyEvent topologyEvent = event.eventData();
MastershipData mastershipData = topologyEvent.getMastershipData();
if (mastershipData == null) {
return Collections.emptyList(); // Not a Mastership Event
}
OnosInstanceId onosInstanceId = topologyEvent.getOnosInstanceId();
Dpid dpid = mastershipData.getDpid();
boolean newMaster = false;
//
// Update the Switch Mastership state:
// - If ADD a MASTER and the Mastership is confirmed by the
// Registry Service, or if the ADD is explicitly CONFIGURED,
// then add to the Mastership map and fetch the postponed
// events from the originating ONOS Instance.
// - Otherwise, remove from the Mastership map, but only if it is
// the current MASTER.
//
if ((event.eventType() == EventEntry.Type.ENTRY_ADD) &&
(mastershipData.getRole() == Role.MASTER)) {
//
// Accept if explicitly configured, otherwise check
// with the Registry Service.
//
if (topologyEvent.getConfigState() == ConfigState.CONFIGURED) {
newMaster = true;
} else {
//
// Check with the Registry Service as well
//
try {
String rc =
registryService.getControllerForSwitch(dpid.value());
if ((rc != null) &&
onosInstanceId.equals(new OnosInstanceId(rc))) {
newMaster = true;
}
} catch (RegistryException e) {
log.error("Caught RegistryException while pre-processing Mastership Event", e);
}
}
}
if (newMaster) {
// Add to the map
switchMastership.put(dpid, onosInstanceId);
return instance.getPostponedEvents(dpid);
}
// Not a Master: eventually remove from the map
OnosInstanceId oldId = switchMastership.get(dpid);
if (onosInstanceId.equals(oldId)) {
switchMastership.remove(dpid);
}
return Collections.emptyList();
}
/**
* Pre-processes a list of events.
*
* @param events the events to pre-process.
* @return a list of pre-processed events.
*/
List<EventEntry<TopologyEvent>> processEvents(
List<EventEntry<TopologyEvent>> events) {
List<EventEntry<TopologyEvent>> result = new LinkedList<>();
//
// Process the events
//
for (EventEntry<TopologyEvent> event : events) {
List<EventEntry<TopologyEvent>> postponedEvents;
// Ignore NO-OP events
if (event.isNoop()) {
continue;
}
TopologyEvent topologyEvent = event.eventData();
OnosInstanceId onosInstanceId = topologyEvent.getOnosInstanceId();
log.debug("Topology event {}: {}", event.eventType(),
topologyEvent);
// Get the ONOS instance state
OnosInstanceLastAddEvents instance =
instanceState.get(onosInstanceId);
if (instance == null) {
instance = new OnosInstanceLastAddEvents(onosInstanceId);
instanceState.put(onosInstanceId, instance);
}
postponedEvents = processMastershipData(instance, event);
//
// Process the event and eventually store it in the
// per-Instance state.
//
if (instance.processEvent(event)) {
result.add(event);
}
// Add the postponed events (if any)
result.addAll(postponedEvents);
}
// Extract and add the previously reordered events
result.addAll(extractReorderedEvents());
return reorderEventsForTopology(result);
}
/**
* Classifies and reorders a list of events, and suppresses matching
* events.
* <p/>
* The result events can be applied to the Topology in the following
* order: REMOVE events followed by ADD events. The ADD events are in the
* natural order to build a Topology: MastershipData, SwitchData,
* PortData, LinkData, HostData. The REMOVE events are in the reverse
* order.
*
* @param events the events to classify and reorder.
* @return the classified and reordered events.
*/
private List<EventEntry<TopologyEvent>> reorderEventsForTopology(
List<EventEntry<TopologyEvent>> events) {
// Local state for computing the final set of events
Map<ByteBuffer, EventEntry<TopologyEvent>> addedMastershipDataEntries =
new HashMap<>();
Map<ByteBuffer, EventEntry<TopologyEvent>> removedMastershipDataEntries =
new HashMap<>();
Map<ByteBuffer, EventEntry<TopologyEvent>> addedSwitchDataEntries =
new HashMap<>();
Map<ByteBuffer, EventEntry<TopologyEvent>> removedSwitchDataEntries =
new HashMap<>();
Map<ByteBuffer, EventEntry<TopologyEvent>> addedPortDataEntries =
new HashMap<>();
Map<ByteBuffer, EventEntry<TopologyEvent>> removedPortDataEntries =
new HashMap<>();
Map<ByteBuffer, EventEntry<TopologyEvent>> addedLinkDataEntries =
new HashMap<>();
Map<ByteBuffer, EventEntry<TopologyEvent>> removedLinkDataEntries =
new HashMap<>();
Map<ByteBuffer, EventEntry<TopologyEvent>> addedHostDataEntries =
new HashMap<>();
Map<ByteBuffer, EventEntry<TopologyEvent>> removedHostDataEntries =
new HashMap<>();
//
// Classify and suppress matching events
//
// NOTE: We intentionally use the event payload as the key ID
// (i.e., we exclude the ONOS Instance ID from the key),
// so we can suppress transient events across multiple ONOS instances.
//
for (EventEntry<TopologyEvent> event : events) {
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();
//
// Extract the events
//
switch (event.eventType()) {
case ENTRY_ADD:
if (mastershipData != null) {
ByteBuffer id = mastershipData.getIDasByteBuffer();
addedMastershipDataEntries.put(id, event);
removedMastershipDataEntries.remove(id);
}
if (switchData != null) {
ByteBuffer id = switchData.getIDasByteBuffer();
addedSwitchDataEntries.put(id, event);
removedSwitchDataEntries.remove(id);
}
if (portData != null) {
ByteBuffer id = portData.getIDasByteBuffer();
addedPortDataEntries.put(id, event);
removedPortDataEntries.remove(id);
}
if (linkData != null) {
ByteBuffer id = linkData.getIDasByteBuffer();
addedLinkDataEntries.put(id, event);
removedLinkDataEntries.remove(id);
}
if (hostData != null) {
ByteBuffer id = hostData.getIDasByteBuffer();
addedHostDataEntries.put(id, event);
removedHostDataEntries.remove(id);
}
break;
case ENTRY_REMOVE:
if (mastershipData != null) {
ByteBuffer id = mastershipData.getIDasByteBuffer();
addedMastershipDataEntries.remove(id);
removedMastershipDataEntries.put(id, event);
}
if (switchData != null) {
ByteBuffer id = switchData.getIDasByteBuffer();
addedSwitchDataEntries.remove(id);
removedSwitchDataEntries.put(id, event);
}
if (portData != null) {
ByteBuffer id = portData.getIDasByteBuffer();
addedPortDataEntries.remove(id);
removedPortDataEntries.put(id, event);
}
if (linkData != null) {
ByteBuffer id = linkData.getIDasByteBuffer();
addedLinkDataEntries.remove(id);
removedLinkDataEntries.put(id, event);
}
if (hostData != null) {
ByteBuffer id = hostData.getIDasByteBuffer();
addedHostDataEntries.remove(id);
removedHostDataEntries.put(id, event);
}
break;
default:
log.error("Unknown topology event {}", event.eventType());
}
}
//
// Prepare the result by adding the events in the appropriate order:
// - First REMOVE, then ADD
// - The REMOVE order is: Host, Link, Port, Switch, Mastership
// - The ADD order is the reverse: Mastership, Switch, Port, Link,
// Host
//
List<EventEntry<TopologyEvent>> result = new LinkedList<>();
result.addAll(removedHostDataEntries.values());
result.addAll(removedLinkDataEntries.values());
result.addAll(removedPortDataEntries.values());
result.addAll(removedSwitchDataEntries.values());
result.addAll(removedMastershipDataEntries.values());
//
result.addAll(addedMastershipDataEntries.values());
result.addAll(addedSwitchDataEntries.values());
result.addAll(addedPortDataEntries.values());
result.addAll(addedLinkDataEntries.values());
result.addAll(addedHostDataEntries.values());
return result;
}
}