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); } }