ImmutableTopology prototype.
Change-Id: I0d9149e2e5145f61a152d80f8afe8dc4bee7b9aa
diff --git a/src/main/java/net/onrc/onos/core/topology/ImmutableTopologySnapshot.java b/src/main/java/net/onrc/onos/core/topology/ImmutableTopologySnapshot.java
new file mode 100644
index 0000000..6fd8d03
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/topology/ImmutableTopologySnapshot.java
@@ -0,0 +1,784 @@
+package net.onrc.onos.core.topology;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+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<MastershipEvent>> mastership;
+
+ // DPID -> Switch
+ private final Map<Dpid, SwitchEvent> switches;
+ private final Map<Dpid, Map<PortNumber, PortEvent>> ports;
+
+ // Index from Port to Host
+ private final Multimap<SwitchPort, HostEvent> hosts;
+ private final Map<MACAddress, HostEvent> mac2Host;
+
+ // SwitchPort -> (type -> Link)
+ private final Map<SwitchPort, Map<String, LinkEvent>> outgoingLinks;
+ private final Map<SwitchPort, Map<String, LinkEvent>> 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 SwitchEvent.
+ *
+ * @param sw Switch to add. (Will be frozen if not already)
+ * @return Builder
+ */
+ public Builder putSwitch(SwitchEvent 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, PortEvent>());
+ }
+ return this;
+ }
+
+ /**
+ * Removes a SwitchEvent 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, PortEvent> 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 PortEvent.
+ *
+ * @param port Port to add. (Will be frozen if not already)
+ * @return Builder
+ */
+ public Builder putPort(PortEvent port) {
+ checkNotNull(port);
+
+ // TODO check parent port and throw TopologyMutationFailed
+
+ Map<PortNumber, PortEvent> 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 PortEvent 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 PortEvent 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, PortEvent> portMap = current.ports.get(dpid);
+ if (portMap != null) {
+ portMap.remove(number);
+ }
+ return this;
+ }
+
+ /**
+ * Puts a LinkEvent.
+ *
+ * @param link LinkEvent
+ * @return Builder
+ */
+ public Builder putLink(LinkEvent 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, LinkEvent>> linkMap,
+ SwitchPort port, LinkEvent link) {
+
+ Map<String, LinkEvent> linksOnPort = linkMap.get(port);
+ if (linksOnPort == null) {
+ linksOnPort = new HashMap<String, LinkEvent>();
+ }
+ linksOnPort.put(link.getType(), link);
+ }
+
+ /**
+ * Removes a LinkEvent 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, LinkEvent> 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 LinkEvent from this snapshot.
+ *
+ * @param link Link to remove
+ * @return Builder
+ */
+ public Builder removeLink(LinkTuple link) {
+ checkNotNull(link);
+
+ Map<String, LinkEvent> links = current.outgoingLinks.get(link.getSrc());
+ if (links == null) {
+ // nothing to do
+ return this;
+ }
+
+ for (LinkEvent linkEvt : links.values()) {
+ removeLink(linkEvt.getLinkTuple(), linkEvt.getType());
+ }
+ return this;
+ }
+
+ /**
+ * Puts a HostEvent.
+ * <p>
+ * Removes attachment points for previous HostEvent and update
+ * them with new HostEvent
+ *
+ * @param host HostEvent
+ * @return Builder
+ */
+ public Builder putHost(HostEvent 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 HostEvent from this snapshot.
+ *
+ * @param mac MACAddress of the Host to remove
+ * @return Builder
+ */
+ public Builder removeHost(MACAddress mac) {
+ checkNotNull(mac);
+
+ HostEvent 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 MastershipEvent
+ * @return Builder
+ */
+ public Builder putSwitchMastershipEvent(MastershipEvent master) {
+ checkNotNull(master);
+
+ SortedSet<MastershipEvent> candidates
+ = current.mastership.get(master.getDpid());
+ if (candidates == null) {
+ // SortedSet, customized so that MASTER MastershipEvent appear
+ // earlier during iteration.
+ candidates = new TreeSet<>(new MastershipEvent.MasterFirstComparator());
+ }
+
+ // 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 MastershipEvent} to remove.
+ *
+ * @param master {@link MastershipEvent} to remove. (Role is ignored)
+ * @return Builder
+ */
+ public Builder removeSwitchMastershipEvent(MastershipEvent master) {
+ checkNotNull(master);
+
+ SortedSet<MastershipEvent> 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<MastershipEvent>> 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, PortEvent>> entry
+ : builder.current.ports.entrySet()) {
+ this.ports.put(entry.getKey(), new HashMap<>(entry.getValue()));
+ }
+
+ this.hosts =
+ HashMultimap.<SwitchPort, HostEvent>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, LinkEvent>> 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, LinkEvent>> 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<MastershipEvent>> 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, PortEvent>> entry
+ : original.ports.entrySet()) {
+ this.ports.put(entry.getKey(), new HashMap<>(entry.getValue()));
+ }
+
+ this.hosts =
+ HashMultimap.<SwitchPort, HostEvent>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, LinkEvent>> 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, LinkEvent>> 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 SwitchEvent getSwitchEvent(final Dpid dpid) {
+ return this.switches.get(dpid);
+ }
+
+ @Override
+ public Collection<SwitchEvent> getAllSwitchEvents() {
+ return Collections.unmodifiableCollection(switches.values());
+ }
+
+ @Override
+ public PortEvent getPortEvent(final SwitchPort port) {
+ return getPortEvent(port.getDpid(), port.getPortNumber());
+ }
+
+ @Override
+ public PortEvent getPortEvent(final Dpid dpid, PortNumber portNumber) {
+ Map<PortNumber, PortEvent> portMap = this.ports.get(dpid);
+ if (portMap != null) {
+ return portMap.get(portNumber);
+ }
+ return null;
+ }
+
+ @Override
+ public Collection<PortEvent> getPortEvents(final Dpid dpid) {
+ Map<PortNumber, PortEvent> portList = ports.get(dpid);
+ if (portList == null) {
+ return Collections.emptyList();
+ }
+ return Collections.unmodifiableCollection(portList.values());
+ }
+
+ @Override
+ public Collection<PortEvent> getAllPortEvents() {
+ List<PortEvent> events = new LinkedList<>();
+ for (Map<PortNumber, PortEvent> cm : ports.values()) {
+ events.addAll(cm.values());
+ }
+ return Collections.unmodifiableCollection(events);
+ }
+
+ @Override
+ public LinkEvent getLinkEvent(final LinkTuple linkId) {
+ Map<String, LinkEvent> links = this.outgoingLinks.get(linkId.getSrc());
+ if (links == null) {
+ return null;
+ }
+
+ // TODO Should we look for Packet link first?
+ // => Not needed unless invariant is broken.
+
+ for (LinkEvent link : links.values()) {
+ if (link.getDst().equals(linkId.getDst())) {
+ return link;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public LinkEvent getLinkEvent(final LinkTuple linkId, final String type) {
+ Map<String, LinkEvent> links = this.outgoingLinks.get(linkId.getSrc());
+ if (links == null) {
+ return null;
+ }
+ return links.get(type);
+ }
+
+ @Override
+ public Collection<LinkEvent> getLinkEventsFrom(SwitchPort srcPort) {
+ Map<String, LinkEvent> links = this.outgoingLinks.get(srcPort);
+ if (links == null) {
+ return Collections.emptyList();
+ }
+
+ return Collections.unmodifiableCollection(links.values());
+ }
+
+ @Override
+ public Collection<LinkEvent> getLinkEventsTo(SwitchPort dstPort) {
+ Map<String, LinkEvent> links = this.incomingLinks.get(dstPort);
+ if (links == null) {
+ return Collections.emptyList();
+ }
+
+ return Collections.unmodifiableCollection(links.values());
+ }
+
+ @Override
+ public Collection<LinkEvent> getLinkEvents(final LinkTuple linkId) {
+ Map<String, LinkEvent> links = this.outgoingLinks.get(linkId.getSrc());
+ if (links == null) {
+ return Collections.emptyList();
+ }
+
+ // unless invariant is broken, this should contain at most 1 element.
+ return Collections.unmodifiableCollection(links.values());
+ }
+
+ @Override
+ public Collection<LinkEvent> getAllLinkEvents() {
+ List<LinkEvent> events = new LinkedList<>();
+ for (Map<String, LinkEvent> cm : outgoingLinks.values()) {
+ events.addAll(cm.values());
+ }
+ return Collections.unmodifiableCollection(events);
+ }
+
+ @Override
+ public HostEvent getHostEvent(final MACAddress mac) {
+ return this.mac2Host.get(mac);
+ }
+
+ @Override
+ public Collection<HostEvent> getHostEvents(SwitchPort port) {
+ return Collections.unmodifiableCollection(this.hosts.get(port));
+ }
+
+ @Override
+ public Collection<HostEvent> getAllHostEvents() {
+ 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
+ */
+ public OnosInstanceId getSwitchMaster(Dpid dpid) {
+ final SortedSet<MastershipEvent> candidates = mastership.get(dpid);
+ if (candidates == null) {
+ return null;
+ }
+ for (MastershipEvent 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);
+ }
+}
diff --git a/src/main/java/net/onrc/onos/core/topology/TopologyMutationFailed.java b/src/main/java/net/onrc/onos/core/topology/TopologyMutationFailed.java
new file mode 100644
index 0000000..b902571
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/topology/TopologyMutationFailed.java
@@ -0,0 +1,38 @@
+package net.onrc.onos.core.topology;
+
+/**
+ * Exception thrown, when topology could not mutated due to unmet preconditions.
+ */
+public class TopologyMutationFailed extends RuntimeException {
+
+ /**
+ * Constructs a new exception with the specified detail message.
+ * {@link RuntimeException#RuntimeException(String)}
+ *
+ * @param message failure description
+ */
+ public TopologyMutationFailed(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new exception with the specified cause.
+ * {@link RuntimeException#RuntimeException(Throwable)}
+ *
+ * @param cause exception causing this.
+ */
+ public TopologyMutationFailed(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message and cause.
+ * {@link RuntimeException#RuntimeException(String, Throwable)}
+ *
+ * @param message failure description
+ * @param cause exception causing this.
+ */
+ public TopologyMutationFailed(String message, Throwable cause) {
+ super(message, cause);
+ }
+}