blob: e115127dcd58d4385b0aab3aefa002fae42ed82c [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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
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.ImmutableMultimap;
import com.google.common.collect.Multimap;
/**
* Immutable Topology snapshot.
*/
@Immutable
public final class ImmutableTopologySnapshot
implements ImmutableTopology, ImmutableInternalTopology {
private static final Logger log = LoggerFactory
.getLogger(ImmutableTopologySnapshot.class);
/**
* Empty Topology.
*/
public static final ImmutableTopologySnapshot EMPTY = new ImmutableTopologySnapshot();
// interface adaptor
private final BaseTopologyAdaptor adaptor;
// Mastership info
// Dpid -> [ (InstanceID, Role) ]
private final Map<Dpid, SortedSet<MastershipData>> mastership;
// DPID -> Switch
private final Map<Dpid, SwitchData> switches;
private final Map<Dpid, Map<PortNumber, PortData>> ports;
// Index from Port to Host
private final Multimap<SwitchPort, HostData> hosts;
private final Map<MACAddress, HostData> mac2Host;
// SwitchPort -> (type -> Link)
private final Map<SwitchPort, Map<String, LinkData>> outgoingLinks;
private final Map<SwitchPort, Map<String, LinkData>> incomingLinks;
// TODO Slice out Topology Builder interface.
// May need to change put*, remove* return values.
/**
* Immutable Topology Builder.
*/
@NotThreadSafe
public static final class Builder {
private final ImmutableTopologySnapshot current;
/**
* Builder to start from empty topology.
*/
private Builder() {
this.current = new ImmutableTopologySnapshot(EMPTY);
}
/**
* Builder to start building from existing topology.
*
* @param original topology to start building from
*/
private Builder(final ImmutableTopologySnapshot original) {
// original must be internally mutable instance
this.current = original;
}
/**
* Gets the current InternalTopology being built.
*
* @return InternalTopology
*/
BaseInternalTopology getCurrentInternal() {
return this.current;
}
/**
* Gets the current Topology being built.
*
* @return Topology
*/
BaseTopology getCurrent() {
return this.current;
}
/**
* Builds the {@link ImmutableTopologySnapshot}.
*
* @return ImmutableTopologySnapshot
*/
public ImmutableTopologySnapshot build() {
return new ImmutableTopologySnapshot(this);
}
// TODO Define error conditions for topology mutation
// - Element was already gone:
// Treat as error or silently ignore?
// - Removing element, where child element still exist:
// Treat as error or silently remove?
/**
* Puts a SwitchData.
*
* @param sw Switch to add. (Will be frozen if not already)
* @return Builder
*/
public Builder putSwitch(SwitchData sw) {
checkNotNull(sw);
current.switches.put(sw.getDpid(), sw.freeze());
if (current.ports.get(sw.getDpid()) == null) {
current.ports.put(sw.getDpid(), new HashMap<PortNumber, PortData>());
}
return this;
}
/**
* Removes a SwitchData from this snapshot.
* <p>
* Will also remove ports, if it has not been removed already.
*
* @param dpid Switch DPID
* @return Builder
*/
public Builder removeSwitch(Dpid dpid) {
checkNotNull(dpid);
current.switches.remove(dpid);
Map<PortNumber, PortData> removedPorts = current.ports.remove(dpid);
if (removedPorts != null && !removedPorts.isEmpty()) {
log.warn("Some ports were removed as side-effect of #removeSwitch({})", dpid);
}
return this;
}
/**
* Puts a PortData.
*
* @param port Port to add. (Will be frozen if not already)
* @return Builder
*/
public Builder putPort(PortData port) {
checkNotNull(port);
// TODO check parent port and throw TopologyMutationFailed
Map<PortNumber, PortData> portMap = current.ports.get(port.getDpid());
if (portMap == null) {
// shouldn't happen but just to be sure
portMap = new HashMap<>();
current.ports.put(port.getDpid(), portMap);
}
portMap.put(port.getPortNumber(), port.freeze());
return this;
}
/**
* Removes a PortData from this snapshot.
*
* @param port SwitchPort to remove
* @return Builder
*/
public Builder removePort(SwitchPort port) {
checkNotNull(port);
removePort(port.getDpid(), port.getPortNumber());
return this;
}
/**
* 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
* @return Builder
*/
public Builder removePort(Dpid dpid, PortNumber number) {
checkNotNull(dpid);
checkNotNull(number);
// TODO sanity check:
// - Links should be removed
// - Host attachment point should be updated.
Map<PortNumber, PortData> portMap = current.ports.get(dpid);
if (portMap != null) {
portMap.remove(number);
}
return this;
}
/**
* Puts a LinkData.
*
* @param link LinkData
* @return Builder
*/
public Builder putLink(LinkData link) {
checkNotNull(link);
// TODO check ports and throw TopologyMutationFailed
// TODO remove host or ignore?
// TODO Add sanity check?
// - There cannot be 2 links in same direction between a port pair.
putLinkMap(current.outgoingLinks, link.getSrc(), link);
putLinkMap(current.incomingLinks, link.getDst(), link);
return this;
}
/**
* 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
*/
private void putLinkMap(Map<SwitchPort, Map<String, LinkData>> linkMap,
SwitchPort port, LinkData link) {
Map<String, LinkData> linksOnPort = linkMap.get(port);
if (linksOnPort == null) {
linksOnPort = new HashMap<String, LinkData>();
linkMap.put(port, linksOnPort);
}
linksOnPort.put(link.getType(), link);
}
/**
* Removes a LinkData from this snapshot.
*
* @param link Link to remove
* @param type type of link to remove
* @return Builder
*/
public Builder removeLink(LinkTuple link, String type) {
checkNotNull(link);
Map<String, LinkData> portLinks
= current.outgoingLinks.get(link.getSrc());
if (portLinks != null) {
// no conditional update here
portLinks.remove(type);
}
portLinks
= current.incomingLinks.get(link.getDst());
if (portLinks != null) {
// no conditional update here
portLinks.remove(type);
}
return this;
}
/**
* Removes a LinkData from this snapshot.
*
* @param link Link to remove
* @return Builder
*/
public Builder removeLink(LinkTuple link) {
checkNotNull(link);
Map<String, LinkData> links = current.outgoingLinks.get(link.getSrc());
if (links == null) {
// nothing to do
return this;
}
for (LinkData linkData : links.values()) {
removeLink(linkData.getLinkTuple(), linkData.getType());
}
return this;
}
/**
* Puts a HostData.
* <p>
* Removes attachment points for previous HostData and update
* them with new HostData
*
* @param host HostData
* @return Builder
*/
public Builder putHost(HostData host) {
checkNotNull(host);
// TODO check Link does not exist on port and throw TopologyMutationFailed
// Host cannot be simply put() to replace instance
// since we need to track attachment point update.
// remove -> put to replace all attachment points, etc. for now.
// remove old attachment points
removeHost(host.getMac());
// add new attachment points
for (SwitchPort port : host.getAttachmentPoints()) {
current.hosts.put(port, host);
}
current.mac2Host.put(host.getMac(), host);
return this;
}
/**
* Removes a HostData from this snapshot.
*
* @param mac MACAddress of the Host to remove
* @return Builder
*/
public Builder removeHost(MACAddress mac) {
checkNotNull(mac);
HostData host = current.mac2Host.remove(mac);
if (host != null) {
for (SwitchPort port : host.getAttachmentPoints()) {
current.hosts.remove(port, host);
}
}
return this;
}
/**
* Puts a mastership change event.
*
* @param master MastershipData
* @return Builder
*/
public Builder putSwitchMastershipData(MastershipData master) {
checkNotNull(master);
SortedSet<MastershipData> candidates
= current.mastership.get(master.getDpid());
if (candidates == null) {
// SortedSet, customized so that MASTER MastershipData appear
// earlier during iteration.
candidates = new TreeSet<>(new MastershipData.MasterFirstComparator());
current.mastership.put(master.getDpid(), candidates);
}
// always replace
candidates.remove(master);
candidates.add(master);
return this;
}
/**
* 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)
* @return Builder
*/
public Builder removeSwitchMastershipData(MastershipData master) {
checkNotNull(master);
SortedSet<MastershipData> candidates
= current.mastership.get(master.getDpid());
if (candidates == null) {
// nothing to do
return this;
}
candidates.remove(master);
return this;
}
}
/**
* Create an empty Topology.
*/
private ImmutableTopologySnapshot() {
mastership = Collections.emptyMap();
switches = Collections.emptyMap();
ports = Collections.emptyMap();
hosts = ImmutableMultimap.of();
mac2Host = Collections.emptyMap();
outgoingLinks = Collections.emptyMap();
incomingLinks = Collections.emptyMap();
this.adaptor = new BaseTopologyAdaptor(this);
}
/**
* Constructor to create instance from Builder.
*
* @param builder Builder
*/
private ImmutableTopologySnapshot(final Builder builder) {
// TODO Change to move semantics to avoid shallow copying or
// Shallow copies should be created using
// Immutable variant or wrapped by Unmodifiable.
//
// If we switched to Immutable* Collections,
// wrapping by Collections.unmodifiableCollection() can be removed.
// shallow copy Set in Map
this.mastership = new HashMap<>(builder.current.mastership.size());
for (Entry<Dpid, SortedSet<MastershipData>> e
: builder.current.mastership.entrySet()) {
this.mastership.put(e.getKey(), new TreeSet<>(e.getValue()));
}
this.switches = new HashMap<>(builder.current.switches);
// shallow copy Map in Map
this.ports = new HashMap<>(builder.current.ports.size());
for (Entry<Dpid, Map<PortNumber, PortData>> entry
: builder.current.ports.entrySet()) {
this.ports.put(entry.getKey(), new HashMap<>(entry.getValue()));
}
this.hosts =
HashMultimap.<SwitchPort, HostData>create(builder.current.hosts);
this.mac2Host = new HashMap<>(builder.current.mac2Host);
// shallow copy Map in Map
this.outgoingLinks = new HashMap<>(builder.current.outgoingLinks.size());
for (Entry<SwitchPort, Map<String, LinkData>> entry
: builder.current.outgoingLinks.entrySet()) {
this.outgoingLinks.put(entry.getKey(), new HashMap<>(entry.getValue()));
}
// shallow copy Map in Map
this.incomingLinks = new HashMap<>(builder.current.incomingLinks.size());
for (Entry<SwitchPort, Map<String, LinkData>> entry
: builder.current.incomingLinks.entrySet()) {
this.incomingLinks.put(entry.getKey(), new HashMap<>(entry.getValue()));
}
this.adaptor = new BaseTopologyAdaptor(this);
}
/**
* Create internally mutable shallow copy of given instance.
* <p>
* Note: only expected to be used by Builder.
*
* @param original instance to copy from
*/
private ImmutableTopologySnapshot(ImmutableTopologySnapshot original) {
// 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 HashMap<>(original.switches);
// shallow copy Map in Map
this.ports = new HashMap<>(original.ports.size());
for (Entry<Dpid, Map<PortNumber, PortData>> entry
: original.ports.entrySet()) {
this.ports.put(entry.getKey(), new HashMap<>(entry.getValue()));
}
this.hosts =
HashMultimap.<SwitchPort, HostData>create(original.hosts);
this.mac2Host = new HashMap<>(original.mac2Host);
// shallow copy Map in Map
this.outgoingLinks = new HashMap<>(original.outgoingLinks.size());
for (Entry<SwitchPort, Map<String, LinkData>> entry
: original.outgoingLinks.entrySet()) {
this.outgoingLinks.put(entry.getKey(), new HashMap<>(entry.getValue()));
}
// shallow copy Map in Map
this.incomingLinks = new HashMap<>(original.incomingLinks.size());
for (Entry<SwitchPort, Map<String, LinkData>> entry
: original.incomingLinks.entrySet()) {
this.incomingLinks.put(entry.getKey(), new HashMap<>(entry.getValue()));
}
this.adaptor = new BaseTopologyAdaptor(this);
}
/**
* Gets the builder starting from empty topology.
*
* @return Builder
*/
public static Builder initialBuilder() {
return new Builder();
}
/**
* Gets the builder starting from this topology.
*
* @return Builder
*/
public Builder builder() {
return new Builder(new ImmutableTopologySnapshot(this));
}
@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) {
Map<PortNumber, PortData> portMap = this.ports.get(dpid);
if (portMap != null) {
return portMap.get(portNumber);
}
return null;
}
@Override
public Collection<PortData> getPortDataEntries(final Dpid dpid) {
Map<PortNumber, PortData> portList = ports.get(dpid);
if (portList == null) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(portList.values());
}
@Override
public Collection<PortData> getAllPortDataEntries() {
List<PortData> dataEntries = new LinkedList<>();
for (Map<PortNumber, PortData> cm : ports.values()) {
dataEntries.addAll(cm.values());
}
return Collections.unmodifiableCollection(dataEntries);
}
@Override
public LinkData getLinkData(final LinkTuple linkId) {
Map<String, LinkData> links = this.outgoingLinks.get(linkId.getSrc());
if (links == null) {
return null;
}
// Should we look for Packet link first?
// => Not needed 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) {
Map<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) {
Map<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) {
Map<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) {
Map<String, LinkData> links = this.outgoingLinks.get(linkId.getSrc());
if (links == null) {
return Collections.emptyList();
}
List<LinkData> linkDataEntries = new ArrayList<>();
for (LinkData ld : links.values()) {
if (ld.getDst().equals(linkId.getDst())) {
linkDataEntries.add(ld);
}
}
// unless invariant is broken, this should contain at most 1 element.
return linkDataEntries;
}
@Override
public Collection<LinkData> getAllLinkDataEntries() {
List<LinkData> dataEntries = new LinkedList<>();
for (Map<String, LinkData> cm : outgoingLinks.values()) {
dataEntries.addAll(cm.values());
}
return Collections.unmodifiableCollection(dataEntries);
}
@Override
public HostData getHostData(final MACAddress mac) {
return this.mac2Host.get(mac);
}
@Override
public Collection<HostData> getHostDataEntries(SwitchPort port) {
return Collections.unmodifiableCollection(this.hosts.get(port));
}
@Override
public Collection<HostData> getAllHostDataEntries() {
return Collections.unmodifiableCollection(mac2Host.values());
}
/**
* Gets the master instance ID for a switch.
*
* @param dpid switch dpid
* @return master instance ID or null if there is no master
*/
@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;
}
// TODO find better way to delegate following to interface adaptor
@Override
public Switch getSwitch(Dpid dpid) {
return adaptor.getSwitch(dpid);
}
@Override
public Iterable<Switch> getSwitches() {
return adaptor.getSwitches();
}
@Override
public Port getPort(Dpid dpid, PortNumber number) {
return adaptor.getPort(dpid, number);
}
@Override
public Port getPort(SwitchPort port) {
return adaptor.getPort(port);
}
@Override
public Collection<Port> getPorts(Dpid dpid) {
return adaptor.getPorts(dpid);
}
@Override
public Link getOutgoingLink(Dpid dpid, PortNumber number) {
return adaptor.getOutgoingLink(dpid, number);
}
@Override
public Link getOutgoingLink(SwitchPort port) {
return adaptor.getOutgoingLink(port);
}
@Override
public Link getOutgoingLink(Dpid dpid, PortNumber number, String type) {
return adaptor.getOutgoingLink(dpid, number, type);
}
@Override
public Link getOutgoingLink(SwitchPort port, String type) {
return adaptor.getOutgoingLink(port, type);
}
@Override
public Collection<Link> getOutgoingLinks(SwitchPort port) {
return adaptor.getOutgoingLinks(port);
}
@Override
public Link getIncomingLink(Dpid dpid, PortNumber number) {
return adaptor.getIncomingLink(dpid, number);
}
@Override
public Link getIncomingLink(SwitchPort port) {
return adaptor.getIncomingLink(port);
}
@Override
public Link getIncomingLink(Dpid dpid, PortNumber number, String type) {
return adaptor.getIncomingLink(dpid, number, type);
}
@Override
public Link getIncomingLink(SwitchPort port, String type) {
return adaptor.getIncomingLink(port, type);
}
@Override
public Collection<Link> getIncomingLinks(SwitchPort port) {
return adaptor.getIncomingLinks(port);
}
@Override
public Link getLink(Dpid srcDpid, PortNumber srcNumber,
Dpid dstDpid, PortNumber dstNumber) {
return adaptor.getLink(srcDpid, srcNumber, dstDpid, dstNumber);
}
@Override
public Link getLink(Dpid srcDpid, PortNumber srcNumber,
Dpid dstDpid, PortNumber dstNumber,
String type) {
return adaptor.getLink(srcDpid, srcNumber, dstDpid, dstNumber, type);
}
@Override
public Iterable<Link> getLinks() {
return adaptor.getLinks();
}
@Override
public Host getHostByMac(MACAddress address) {
return adaptor.getHostByMac(address);
}
@Override
public Iterable<Host> getHosts() {
return adaptor.getHosts();
}
@Override
public Collection<Host> getHosts(SwitchPort port) {
return adaptor.getHosts(port);
}
}