ONOS-4971: Synthetic Link Data -- WIP
- Breaking out UiLink to subclasses for device links, host links, region links, region-device links,
- (soon, also peer links).
- Augmenting UiLinkId to include regions as endpoints.
- Introduced UiSynthLink to encapsulate synthetic links bound to regions.
- Model Cache now computes synthetic links from the underlying link data.
- Added endPointA/B() and type() methods to UiLink.
- Updated topo2CurrentRegion response to include synth-links for the region.
Change-Id: Ifa62a15fbe0a58b134d92278b201fa7a72cbfa83
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
index 708fd18..3297608 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
@@ -19,6 +19,7 @@
import org.onosproject.cluster.NodeId;
import org.onosproject.net.DeviceId;
import org.onosproject.net.HostId;
+import org.onosproject.net.PortNumber;
import org.onosproject.net.region.RegionId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,9 +31,12 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.onosproject.ui.model.topo.UiLinkId.uiLinkId;
/**
* Represents the overall network topology.
@@ -59,7 +63,11 @@
private final Map<RegionId, UiRegion> regionLookup = new HashMap<>();
private final Map<DeviceId, UiDevice> deviceLookup = new HashMap<>();
private final Map<HostId, UiHost> hostLookup = new HashMap<>();
- private final Map<UiLinkId, UiLink> linkLookup = new HashMap<>();
+ private final Map<UiLinkId, UiDeviceLink> devLinkLookup = new HashMap<>();
+ private final Map<UiLinkId, UiEdgeLink> edgeLinkLookup = new HashMap<>();
+
+ // a cache of the computed synthetic links
+ private final List<UiSynthLink> synthLinks = new ArrayList<>();
// a container for devices, hosts, etc. belonging to no region
private final UiRegion nullRegion = new UiRegion(this, null);
@@ -72,7 +80,9 @@
.add("#regions", regionCount())
.add("#devices", deviceLookup.size())
.add("#hosts", hostLookup.size())
- .add("#links", linkLookup.size())
+ .add("#dev-links", devLinkLookup.size())
+ .add("#edge-links", edgeLinkLookup.size())
+ .add("#synth-links", synthLinks.size())
.toString();
}
@@ -91,7 +101,10 @@
regionLookup.clear();
deviceLookup.clear();
hostLookup.clear();
- linkLookup.clear();
+ devLinkLookup.clear();
+ edgeLinkLookup.clear();
+
+ synthLinks.clear();
nullRegion.destroy();
}
@@ -261,54 +274,96 @@
return deviceLookup.size();
}
+
/**
- * Returns all links in the model.
+ * Returns all device links in the model.
*
- * @return all links
+ * @return all device links
*/
- public Set<UiLink> allLinks() {
- return new HashSet<>(linkLookup.values());
+ public Set<UiDeviceLink> allDeviceLinks() {
+ return new HashSet<>(devLinkLookup.values());
}
/**
- * Returns the link with the specified identifier, or null if no such
- * link exists.
+ * Returns the device link with the specified identifier, or null if no
+ * such link exists.
*
* @param id the canonicalized link identifier
- * @return corresponding UI link
+ * @return corresponding UI device link
*/
- public UiLink findLink(UiLinkId id) {
- return linkLookup.get(id);
+ public UiDeviceLink findDeviceLink(UiLinkId id) {
+ return devLinkLookup.get(id);
}
/**
- * Adds the given UI link to the topology model.
+ * Returns the edge link with the specified identifier, or null if no
+ * such link exists.
*
- * @param uiLink link to add
+ * @param id the canonicalized link identifier
+ * @return corresponding UI edge link
*/
- public void add(UiLink uiLink) {
- linkLookup.put(uiLink.id(), uiLink);
+ public UiEdgeLink findEdgeLink(UiLinkId id) {
+ return edgeLinkLookup.get(id);
}
/**
- * Removes the given UI link from the model.
+ * Adds the given UI device link to the topology model.
*
- * @param uiLink link to remove
+ * @param uiDeviceLink link to add
*/
- public void remove(UiLink uiLink) {
- UiLink link = linkLookup.remove(uiLink.id());
+ public void add(UiDeviceLink uiDeviceLink) {
+ devLinkLookup.put(uiDeviceLink.id(), uiDeviceLink);
+ }
+
+ /**
+ * Adds the given UI edge link to the topology model.
+ *
+ * @param uiEdgeLink link to add
+ */
+ public void add(UiEdgeLink uiEdgeLink) {
+ edgeLinkLookup.put(uiEdgeLink.id(), uiEdgeLink);
+ }
+
+ /**
+ * Removes the given UI device link from the model.
+ *
+ * @param uiDeviceLink link to remove
+ */
+ public void remove(UiDeviceLink uiDeviceLink) {
+ UiDeviceLink link = devLinkLookup.remove(uiDeviceLink.id());
if (link != null) {
link.destroy();
}
}
/**
- * Returns the number of links configured in the topology.
+ * Removes the given UI edge link from the model.
*
- * @return number of links
+ * @param uiEdgeLink link to remove
*/
- public int linkCount() {
- return linkLookup.size();
+ public void remove(UiEdgeLink uiEdgeLink) {
+ UiEdgeLink link = edgeLinkLookup.remove(uiEdgeLink.id());
+ if (link != null) {
+ link.destroy();
+ }
+ }
+
+ /**
+ * Returns the number of device links configured in the topology.
+ *
+ * @return number of device links
+ */
+ public int deviceLinkCount() {
+ return devLinkLookup.size();
+ }
+
+ /**
+ * Returns the number of edge links configured in the topology.
+ *
+ * @return number of edge links
+ */
+ public int edgeLinkCount() {
+ return edgeLinkLookup.size();
}
/**
@@ -405,22 +460,198 @@
}
/**
- * Returns the set of UI links with the given identifiers.
+ * Returns the set of UI device links with the given identifiers.
*
* @param uiLinkIds link identifiers
- * @return set of matching UI link instances
+ * @return set of matching UI device link instances
*/
- Set<UiLink> linkSet(Set<UiLinkId> uiLinkIds) {
- Set<UiLink> uiLinks = new HashSet<>();
+ Set<UiDeviceLink> linkSet(Set<UiLinkId> uiLinkIds) {
+ Set<UiDeviceLink> result = new HashSet<>();
for (UiLinkId id : uiLinkIds) {
- UiLink link = linkLookup.get(id);
+ UiDeviceLink link = devLinkLookup.get(id);
if (link != null) {
- uiLinks.add(link);
+ result.add(link);
} else {
- log.warn(E_UNMAPPED, "link", id);
+ log.warn(E_UNMAPPED, "device link", id);
}
}
- return uiLinks;
+ return result;
+ }
+
+ /**
+ * Uses the device-device links and data about the regions to compute the
+ * set of synthetic links that are required per region.
+ */
+ public void computeSynthLinks() {
+ List<UiSynthLink> slinks = new ArrayList<>();
+ allDeviceLinks().forEach((link) -> {
+ UiSynthLink synthetic = inferSyntheticLink(link);
+ slinks.add(synthetic);
+ log.debug("Synthetic link: {}", synthetic);
+ });
+
+ synthLinks.clear();
+ synthLinks.addAll(slinks);
+ }
+
+ private UiSynthLink inferSyntheticLink(UiDeviceLink link) {
+ /*
+ Look at the containment hierarchy of each end of the link. Find the
+ common ancestor region R. A synthetic link will be added to R, based
+ on the "next" node back down the branch...
+
+ S1 --- S2 * in the same region ...
+ : :
+ R R return S1 --- S2 (same link instance)
+
+
+ S1 --- S2 * in different regions (R1, R2) at same level
+ : :
+ R1 R2 return R1 --- R2
+ : :
+ R R
+
+ S1 --- S2 * in different regions at different levels
+ : :
+ R1 R2 return R1 --- R3
+ : :
+ R R3
+ :
+ R
+
+ S1 --- S2 * in different regions at different levels
+ : :
+ R R2 return S1 --- R2
+ :
+ R
+
+ */
+ DeviceId a = link.deviceA();
+ DeviceId b = link.deviceB();
+ List<RegionId> aBranch = ancestors(a);
+ List<RegionId> bBranch = ancestors(b);
+ if (aBranch == null || bBranch == null) {
+ return null;
+ }
+
+ return makeSynthLink(link, aBranch, bBranch);
+ }
+
+ // package private for unit testing
+ UiSynthLink makeSynthLink(UiDeviceLink orig,
+ List<RegionId> aBranch,
+ List<RegionId> bBranch) {
+
+ final int aSize = aBranch.size();
+ final int bSize = bBranch.size();
+ final int min = Math.min(aSize, bSize);
+
+ int index = 0;
+ RegionId commonRegion = aBranch.get(index);
+
+ while (true) {
+ int next = index + 1;
+ if (next == min) {
+ // no more pairs of regions left to test
+ break;
+ }
+ RegionId rA = aBranch.get(next);
+ RegionId rB = bBranch.get(next);
+ if (rA.equals(rB)) {
+ commonRegion = rA;
+ index++;
+ } else {
+ break;
+ }
+ }
+
+
+ int endPointIndex = index + 1;
+ UiLinkId linkId;
+ UiLink link;
+
+ if (endPointIndex < aSize) {
+ // the A endpoint is a subregion
+ RegionId aRegion = aBranch.get(endPointIndex);
+
+ if (endPointIndex < bSize) {
+ // the B endpoint is a subregion
+ RegionId bRegion = bBranch.get(endPointIndex);
+
+ linkId = uiLinkId(aRegion, bRegion);
+ link = new UiRegionLink(this, linkId);
+
+ } else {
+ // the B endpoint is the device
+ DeviceId dB = orig.deviceB();
+ PortNumber pB = orig.portB();
+
+ linkId = uiLinkId(aRegion, dB, pB);
+ link = new UiRegionDeviceLink(this, linkId);
+ }
+
+ } else {
+ // the A endpoint is the device
+ DeviceId dA = orig.deviceA();
+ PortNumber pA = orig.portA();
+
+ if (endPointIndex < bSize) {
+ // the B endpoint is a subregion
+ RegionId bRegion = bBranch.get(endPointIndex);
+
+ linkId = uiLinkId(bRegion, dA, pA);
+ link = new UiRegionDeviceLink(this, linkId);
+
+ } else {
+ // the B endpoint is the device
+ // (so, we can just use the original device-device link...)
+
+ link = orig;
+ }
+ }
+ return new UiSynthLink(commonRegion, link);
+ }
+
+ private List<RegionId> ancestors(DeviceId id) {
+ // return the ancestor chain from this device to root region
+ UiDevice dev = findDevice(id);
+ if (dev == null) {
+ log.warn("Unable to find cached device with ID %s", id);
+ return null;
+ }
+
+ UiRegion r = dev.uiRegion();
+ List<RegionId> result = new ArrayList<>();
+ while (r != null && !r.isRoot()) {
+ result.add(0, r.id());
+ r = r.parentRegion();
+ }
+ // finally add root region, since this is the grand-daddy of them all
+ result.add(0, UiRegion.NULL_ID);
+ return result;
+ }
+
+
+ /**
+ * Returns the synthetic links associated with the specified region.
+ *
+ * @param regionId the region ID
+ * @return synthetic links for this region
+ */
+ public List<UiSynthLink> findSynthLinks(RegionId regionId) {
+ return synthLinks.stream()
+ .filter(s -> Objects.equals(regionId, s.regionId()))
+ .collect(Collectors.toList());
+ }
+
+
+ /**
+ * Returns the number of synthetic links in the topology.
+ *
+ * @return the synthetic link count
+ */
+ public int synthLinkCount() {
+ return synthLinks.size();
}
/**
@@ -452,13 +683,22 @@
sb.append(INDENT_2).append(h).append(EOL);
}
- sb.append(INDENT_1).append("Links").append(EOL);
- for (UiLink link : linkLookup.values()) {
+ sb.append(INDENT_1).append("Device Links").append(EOL);
+ for (UiLink link : devLinkLookup.values()) {
+ sb.append(INDENT_2).append(link).append(EOL);
+ }
+
+ sb.append(INDENT_1).append("Edge Links").append(EOL);
+ for (UiLink link : edgeLinkLookup.values()) {
+ sb.append(INDENT_2).append(link).append(EOL);
+ }
+
+ sb.append(INDENT_1).append("Synth Links").append(EOL);
+ for (UiSynthLink link : synthLinks) {
sb.append(INDENT_2).append(link).append(EOL);
}
sb.append("------").append(EOL);
return sb.toString();
}
-
}