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