blob: 9efd9119fe48a05fc25337e90cb11d40d6c1a61d [file] [log] [blame]
package net.onrc.onos.core.topology;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.concurrent.GuardedBy;
import net.floodlightcontroller.core.IFloodlightProviderService.Role;
import net.floodlightcontroller.util.MACAddress;
import net.onrc.onos.core.util.Dpid;
import net.onrc.onos.core.util.LinkTuple;
import net.onrc.onos.core.util.OnosInstanceId;
import net.onrc.onos.core.util.PortNumber;
import net.onrc.onos.core.util.SwitchPort;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
// TODO add TopologyManager, etc. to populate Mastership information.
/**
* Class to represent an instance of Topology Snapshot.
*/
public class TopologyImpl implements MutableTopology, MutableInternalTopology {
private static final Logger log = LoggerFactory.getLogger(TopologyImpl.class);
// TODO Revisit Map types after implementing CoW/lock-free
// Mastership info
// Dpid -> [ (InstanceID, Role) ]
private final Map<Dpid, SortedSet<MastershipData>> mastership;
// DPID -> Switch
private final ConcurrentMap<Dpid, SwitchData> switches;
private final ConcurrentMap<Dpid, ConcurrentMap<PortNumber, PortData>> ports;
// Index from Port to Host
private final Multimap<SwitchPort, HostData> hosts;
private final ConcurrentMap<MACAddress, HostData> mac2Host;
// SwitchPort -> (type -> Link)
private final ConcurrentMap<SwitchPort, ConcurrentMap<String, LinkData>> outgoingLinks;
private final ConcurrentMap<SwitchPort, ConcurrentMap<String, LinkData>> incomingLinks;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
// TODO use the write lock after refactor
private final Lock writeLock = readWriteLock.writeLock();
/**
* Create an empty Topology.
*/
public TopologyImpl() {
mastership = new HashMap<>();
// TODO: Does these object need to be stored in Concurrent Collection?
switches = new ConcurrentHashMap<>();
ports = new ConcurrentHashMap<>();
hosts = Multimaps.synchronizedMultimap(
HashMultimap.<SwitchPort, HostData>create());
mac2Host = new ConcurrentHashMap<>();
outgoingLinks = new ConcurrentHashMap<>();
incomingLinks = new ConcurrentHashMap<>();
}
/**
* Create a shallow copy of given Topology.
*
* @param original Topology
*/
public TopologyImpl(TopologyImpl original) {
original.acquireReadLock();
try {
// shallow copy Set in Map
this.mastership = new HashMap<>(original.mastership.size());
for (Entry<Dpid, SortedSet<MastershipData>> e
: original.mastership.entrySet()) {
this.mastership.put(e.getKey(), new TreeSet<>(e.getValue()));
}
this.switches = new ConcurrentHashMap<>(original.switches);
// shallow copy Map in Map
this.ports = new ConcurrentHashMap<>(original.ports.size());
for (Entry<Dpid, ConcurrentMap<PortNumber, PortData>> entry
: original.ports.entrySet()) {
this.ports.put(entry.getKey(), new ConcurrentHashMap<>(entry.getValue()));
}
this.hosts = Multimaps.synchronizedMultimap(
HashMultimap.<SwitchPort, HostData>create(original.hosts));
this.mac2Host = new ConcurrentHashMap<>(original.mac2Host);
// shallow copy Map in Map
this.outgoingLinks = new ConcurrentHashMap<>(original.outgoingLinks.size());
for (Entry<SwitchPort, ConcurrentMap<String, LinkData>> entry
: original.outgoingLinks.entrySet()) {
this.outgoingLinks.put(entry.getKey(), new ConcurrentHashMap<>(entry.getValue()));
}
// shallow copy Map in Map
this.incomingLinks = new ConcurrentHashMap<>(original.incomingLinks.size());
for (Entry<SwitchPort, ConcurrentMap<String, LinkData>> entry
: original.incomingLinks.entrySet()) {
this.incomingLinks.put(entry.getKey(), new ConcurrentHashMap<>(entry.getValue()));
}
} finally {
original.releaseReadLock();
}
}
@Override
public Switch getSwitch(Dpid dpid) {
final SwitchData sw = switches.get(dpid);
if (sw != null) {
return new SwitchImpl(this, dpid);
} else {
return null;
}
}
@Override
public Iterable<Switch> getSwitches() {
List<Switch> list = new ArrayList<>(switches.size());
for (SwitchData elm : switches.values()) {
list.add(new SwitchImpl(this, elm.getDpid()));
}
return list;
}
@Override
public Port getPort(Dpid dpid, PortNumber number) {
ConcurrentMap<PortNumber, PortData> portMap = ports.get(dpid);
if (portMap != null) {
final PortData port = portMap.get(number);
if (port != null) {
return new PortImpl(this, port.getSwitchPort());
}
}
return null;
}
@Override
public Port getPort(SwitchPort port) {
return getPort(port.getDpid(), port.getPortNumber());
}
@Override
public Collection<Port> getPorts(Dpid dpid) {
ConcurrentMap<PortNumber, PortData> portMap = ports.get(dpid);
if (portMap == null) {
return Collections.emptyList();
}
List<Port> list = new ArrayList<>(portMap.size());
for (PortData elm : portMap.values()) {
list.add(new PortImpl(this, elm.getSwitchPort()));
}
return list;
}
@Override
public Link getOutgoingLink(Dpid dpid, PortNumber number) {
return getOutgoingLink(new SwitchPort(dpid, number));
}
@Override
public Link getOutgoingLink(SwitchPort port) {
Map<String, LinkData> links = outgoingLinks.get(port);
return getPacketLinkIfExists(links);
}
// TODO remove when we no longer need packet fall back behavior
/**
* Gets the "packet" link if such exists,
* if not return whichever link is found first.
*
* @param links Collection of links to search from
* @return Link instance found or null if no link exists
*/
private Link getPacketLinkIfExists(Map<String, LinkData> links) {
if (links == null) {
return null;
}
LinkData link = links.get(TopologyElement.TYPE_PACKET_LAYER);
if (link != null) {
// return packet link
return new LinkImpl(this, link.getLinkTuple());
} else {
// return whatever found
Iterator<LinkData> it = links.values().iterator();
if (it.hasNext()) {
return new LinkImpl(this, it.next().getLinkTuple());
}
}
return null;
}
@Override
public Link getOutgoingLink(Dpid dpid, PortNumber number, String type) {
return getOutgoingLink(new SwitchPort(dpid, number), type);
}
@Override
public Link getOutgoingLink(SwitchPort port, String type) {
Map<String, LinkData> links = outgoingLinks.get(port);
final LinkData link = links.get(type);
if (link != null) {
return new LinkImpl(this, link.getLinkTuple());
}
return null;
}
@Override
public Collection<Link> getOutgoingLinks(SwitchPort port) {
ConcurrentMap<String, LinkData> typeMap = outgoingLinks.get(port);
if (typeMap == null) {
return Collections.emptyList();
}
return toLinkImpls(typeMap.values());
}
/**
* Converts collection of LinkData to collection of LinkImpls.
*
* @param links collection of LinkData
* @return collection of LinkImpls
*/
private Collection<Link> toLinkImpls(final Collection<LinkData> links) {
if (links == null) {
return Collections.emptyList();
}
List<Link> list = new ArrayList<>(links.size());
for (LinkData elm : links) {
list.add(new LinkImpl(this, elm.getLinkTuple()));
}
return list;
}
@Override
public Link getIncomingLink(Dpid dpid, PortNumber number) {
return getIncomingLink(new SwitchPort(dpid, number));
}
@Override
public Link getIncomingLink(SwitchPort port) {
Map<String, LinkData> links = incomingLinks.get(port);
return getPacketLinkIfExists(links);
}
@Override
public Link getIncomingLink(Dpid dpid, PortNumber number, String type) {
return getIncomingLink(new SwitchPort(dpid, number), type);
}
@Override
public Link getIncomingLink(SwitchPort port, String type) {
Map<String, LinkData> links = incomingLinks.get(port);
final LinkData link = links.get(type);
if (link != null) {
return new LinkImpl(this, link.getLinkTuple());
}
return null;
}
@Override
public Collection<Link> getIncomingLinks(SwitchPort port) {
ConcurrentMap<String, LinkData> typeMap = incomingLinks.get(port);
if (typeMap == null) {
return Collections.emptyList();
}
return toLinkImpls(typeMap.values());
}
@Override
public Link getLink(Dpid srcDpid, PortNumber srcNumber,
Dpid dstDpid, PortNumber dstNumber) {
final SwitchPort dstSwitchPort = new SwitchPort(dstDpid, dstNumber);
Collection<Link> links = getOutgoingLinks(new SwitchPort(srcDpid, srcNumber));
for (Link link : links) {
if (link == null) {
continue;
}
if (link.getDstPort().getSwitchPort().equals(dstSwitchPort)) {
return link;
}
}
return null;
}
@Override
public Link getLink(Dpid srcDpid, PortNumber srcNumber,
Dpid dstDpid, PortNumber dstNumber,
String type) {
Link link = getOutgoingLink(srcDpid, srcNumber, type);
if (link == null) {
return null;
}
if (!link.getDstSwitch().getDpid().equals(dstDpid)) {
return null;
}
if (!link.getDstPort().getNumber().equals(dstNumber)) {
return null;
}
return link;
}
@Override
public Iterable<Link> getLinks() {
List<Link> links = new ArrayList<>();
for (Map<String, LinkData> portLinks : outgoingLinks.values()) {
if (portLinks == null) {
continue;
}
for (LinkData elm : portLinks.values()) {
links.add(new LinkImpl(this, elm.getLinkTuple()));
}
}
return links;
}
@Override
public Host getHostByMac(MACAddress address) {
HostData host = mac2Host.get(address);
if (host != null) {
return new HostImpl(this, address);
}
return null;
}
@Override
public Iterable<Host> getHosts() {
return toHostImpls(mac2Host.values());
}
/**
* Converts collection of HostData to collection of HostImpl.
*
* @param events collection of HostData
* @return collection of HostImpl
*/
private List<Host> toHostImpls(Collection<HostData> events) {
if (events == null) {
return Collections.emptyList();
}
List<Host> list = new ArrayList<>(events.size());
for (HostData elm : events) {
list.add(new HostImpl(this, elm.getMac(), elm.getIp()));
}
return list;
}
@Override
public Collection<Host> getHosts(SwitchPort port) {
return toHostImpls(hosts.get(port));
}
@Override
public SwitchData getSwitchData(final Dpid dpid) {
return this.switches.get(dpid);
}
@Override
public Collection<SwitchData> getAllSwitchDataEntries() {
return Collections.unmodifiableCollection(switches.values());
}
@Override
public PortData getPortData(final SwitchPort port) {
return getPortData(port.getDpid(), port.getPortNumber());
}
@Override
public PortData getPortData(final Dpid dpid, PortNumber portNumber) {
ConcurrentMap<PortNumber, PortData> portMap = this.ports.get(dpid);
if (portMap != null) {
return portMap.get(portNumber);
}
return null;
}
@Override
public Collection<PortData> getPortDataEntries(final Dpid dpid) {
ConcurrentMap<PortNumber, PortData> portList = ports.get(dpid);
if (portList == null) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(portList.values());
}
@Override
public Collection<PortData> getAllPortDataEntries() {
List<PortData> events = new LinkedList<>();
for (ConcurrentMap<PortNumber, PortData> cm : ports.values()) {
events.addAll(cm.values());
}
return Collections.unmodifiableCollection(events);
}
@Override
public LinkData getLinkData(final LinkTuple linkId) {
ConcurrentMap<String, LinkData> links = this.outgoingLinks.get(linkId.getSrc());
if (links == null) {
return null;
}
// TODO Should we look for Packet link first?
// Not unless invariant is broken.
for (LinkData link : links.values()) {
if (link.getDst().equals(linkId.getDst())) {
return link;
}
}
return null;
}
@Override
public LinkData getLinkData(final LinkTuple linkId, final String type) {
ConcurrentMap<String, LinkData> links = this.outgoingLinks.get(linkId.getSrc());
if (links == null) {
return null;
}
LinkData link = links.get(type);
if (link.getDst().equals(linkId.getDst())) {
return link;
}
return null;
}
@Override
public Collection<LinkData> getLinkDataEntriesFrom(SwitchPort srcPort) {
ConcurrentMap<String, LinkData> links = this.outgoingLinks.get(srcPort);
if (links == null) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(links.values());
}
@Override
public Collection<LinkData> getLinkDataEntriesTo(SwitchPort dstPort) {
ConcurrentMap<String, LinkData> links = this.incomingLinks.get(dstPort);
if (links == null) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(links.values());
}
@Override
public Collection<LinkData> getLinkDataEntries(final LinkTuple linkId) {
ConcurrentMap<String, LinkData> links = this.outgoingLinks.get(linkId.getSrc());
if (links == null) {
return Collections.emptyList();
}
List<LinkData> linkDataEntries = new ArrayList<>();
for (LinkData e : links.values()) {
if (e.getDst().equals(linkId.getDst())) {
linkDataEntries.add(e);
}
}
// unless invariant is broken, this should contain at most 1 element.
return linkDataEntries;
}
@Override
public Collection<LinkData> getAllLinkDataEntries() {
List<LinkData> events = new LinkedList<>();
for (ConcurrentMap<String, LinkData> cm : outgoingLinks.values()) {
events.addAll(cm.values());
}
return Collections.unmodifiableCollection(events);
}
@Override
public HostData getHostData(final MACAddress mac) {
return this.mac2Host.get(mac);
}
@Override
public Collection<HostData> getHostDataEntries(SwitchPort port) {
return Collections.unmodifiableCollection(hosts.get(port));
}
@Override
public Collection<HostData> getAllHostDataEntries() {
return Collections.unmodifiableCollection(mac2Host.values());
}
@Override
public OnosInstanceId getSwitchMaster(Dpid dpid) {
final SortedSet<MastershipData> candidates = mastership.get(dpid);
if (candidates == null) {
return null;
}
for (MastershipData candidate : candidates) {
if (candidate.getRole() == Role.MASTER) {
return candidate.getOnosInstanceId();
}
}
return null;
}
/**
* Puts a SwitchData.
*
* @param sw Switch to add. (Will be frozen if not already)
*/
@GuardedBy("writeLock")
protected void putSwitch(SwitchData sw) {
// TODO isFrozen check once we implement CoW/lock-free
switches.put(sw.getDpid(), sw.freeze());
ports.putIfAbsent(sw.getDpid(), new ConcurrentHashMap<PortNumber, PortData>());
}
/**
* Removes a SwitchData from this snapshot.
* <p/>
* Will also remove ports, if it has not been removed already.
*
* @param dpid Switch DPID
*/
@GuardedBy("writeLock")
protected void removeSwitch(Dpid dpid) {
// TODO isFrozen check once we implement CoW/lock-free
switches.remove(dpid);
ConcurrentMap<PortNumber, PortData> removedPorts = ports.remove(dpid);
if (removedPorts != null && !removedPorts.isEmpty()) {
log.warn("Some ports were removed as side-effect of #removeSwitch({})", dpid);
}
}
/**
* Puts a PortData.
*
* @param port Port to add. (Will be frozen if not already)
*/
@GuardedBy("writeLock")
protected void putPort(PortData port) {
ConcurrentMap<PortNumber, PortData> portMap = ports.get(port.getDpid());
if (portMap == null) {
portMap = new ConcurrentHashMap<>();
ConcurrentMap<PortNumber, PortData> existing
= ports.putIfAbsent(port.getDpid(), portMap);
if (existing != null) {
// port map was added concurrently, using theirs
portMap = existing;
}
}
portMap.put(port.getPortNumber(), port.freeze());
}
/**
* Removes a PortData from this snapshot.
*
* @param port SwitchPort to remove
*/
@GuardedBy("writeLock")
protected void removePort(SwitchPort port) {
removePort(port.getDpid(), port.getPortNumber());
}
/**
* Removes a PortData from this snapshot.
* <p/>
* Will also remove ports, if it has not been removed already.
*
* @param dpid Switch DPID
* @param number PortNumber
*/
@GuardedBy("writeLock")
protected void removePort(Dpid dpid, PortNumber number) {
// TODO sanity check Host attachment point.
ConcurrentMap<PortNumber, PortData> portMap = ports.get(dpid);
if (portMap != null) {
portMap.remove(number);
}
}
/**
* Puts a LinkData.
*
* @param link LinkData
*/
@GuardedBy("writeLock")
protected void putLink(LinkData link) {
// TODO Do sanity check?
// - There cannot be 2 links in same direction between a port pair.
putLinkMap(outgoingLinks, link.getSrc(), link);
putLinkMap(incomingLinks, link.getDst(), link);
}
/**
* Helper method to update outgoingLinks, incomingLinks.
*
* @param linkMap outgoingLinks or incomingLinks to update
* @param port {@code linkMap} key to update
* @param link Link to add
*/
@GuardedBy("writeLock")
private void putLinkMap(ConcurrentMap<SwitchPort, ConcurrentMap<String, LinkData>> linkMap,
SwitchPort port, LinkData link) {
ConcurrentMap<String, LinkData> linksOnPort = linkMap.get(port);
if (linksOnPort == null) {
linksOnPort = new ConcurrentHashMap<>(4);
ConcurrentMap<String, LinkData> existing
= linkMap.putIfAbsent(
port,
linksOnPort);
if (existing != null) {
linksOnPort = existing;
}
}
linksOnPort.put(link.getType(), link);
}
/**
* Removes a LinkData from this snapshot.
*
* @param link Link to remove
* @param type type of link to remove
*/
@GuardedBy("writeLock")
protected void removeLink(LinkTuple link, String type) {
ConcurrentMap<String, LinkData> portLinks
= outgoingLinks.get(link.getSrc());
if (portLinks != null) {
// no conditional update here
portLinks.remove(type);
}
portLinks
= incomingLinks.get(link.getDst());
if (portLinks != null) {
// no conditional update here
portLinks.remove(type);
}
}
/**
* Removes a LinkData from this snapshot.
*
* @param link Link to remove
*/
@GuardedBy("writeLock")
protected void removeLink(LinkTuple link) {
Collection<LinkData> links = getLinkDataEntries(link);
for (LinkData l : links) {
removeLink(link, l.getType());
}
}
/**
* Puts a HostData.
* <p/>
* Removes attachment points for previous HostData and update
* them with new HostData
*
* @param host HostData
*/
@GuardedBy("writeLock")
protected void putHost(HostData host) {
// Host cannot be simply put() to replace instance since it has mobility.
// Simply remove -> put for now.
// remove old attachment points
removeHost(host.getMac());
// add new attachment points
for (SwitchPort port : host.getAttachmentPoints()) {
hosts.put(port, host);
}
mac2Host.put(host.getMac(), host);
}
/**
* Removes a HostData from this snapshot.
*
* @param mac MACAddress of the Host to remove
*/
@GuardedBy("writeLock")
protected void removeHost(MACAddress mac) {
HostData host = mac2Host.remove(mac);
if (host != null) {
for (SwitchPort port : host.getAttachmentPoints()) {
hosts.remove(port, host);
}
}
}
/**
* Puts a mastership change event.
*
* @param master MastershipData
*/
@GuardedBy("writeLock")
protected void putSwitchMastershipData(MastershipData master) {
checkNotNull(master);
SortedSet<MastershipData> candidates
= mastership.get(master.getDpid());
if (candidates == null) {
// SortedSet, customized so that MASTER MastershipData appear
// earlier during iteration.
candidates = new TreeSet<>(new MastershipData.MasterFirstComparator());
}
// always replace
candidates.remove(master);
candidates.add(master);
}
/**
* Removes a mastership change event.
* <p>
* Note: Only Dpid and OnosInstanceId will be used to identify the
* {@link MastershipData} to remove.
*
* @param master {@link MastershipData} to remove. (Role is ignored)
*/
@GuardedBy("writeLock")
protected void removeSwitchMastershipData(MastershipData master) {
checkNotNull(master);
SortedSet<MastershipData> candidates
= mastership.get(master.getDpid());
if (candidates == null) {
// nothing to do
return;
}
candidates.remove(master);
}
@Override
public void acquireReadLock() {
readLock.lock();
}
@Override
public void releaseReadLock() {
readLock.unlock();
}
protected void acquireWriteLock() {
writeLock.lock();
}
protected void releaseWriteLock() {
writeLock.unlock();
}
}