| /* |
| * Copyright 2016-present Open Networking Laboratory |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.onosproject.ui.model.topo; |
| |
| 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; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| 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. |
| */ |
| public class UiTopology extends UiElement { |
| |
| private static final String INDENT_1 = " "; |
| private static final String INDENT_2 = " "; |
| private static final String EOL = String.format("%n"); |
| |
| private static final String E_UNMAPPED = |
| "Attempting to retrieve unmapped {}: {}"; |
| |
| private static final String DEFAULT_TOPOLOGY_ID = "TOPOLOGY-0"; |
| |
| private static final Logger log = LoggerFactory.getLogger(UiTopology.class); |
| |
| private static final Comparator<UiClusterMember> CLUSTER_MEMBER_COMPARATOR = |
| (o1, o2) -> o1.idAsString().compareTo(o2.idAsString()); |
| |
| |
| // top level mappings of topology elements by ID |
| private final Map<NodeId, UiClusterMember> cnodeLookup = new HashMap<>(); |
| 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, 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); |
| |
| |
| @Override |
| public String toString() { |
| return toStringHelper(this) |
| .add("#cnodes", clusterMemberCount()) |
| .add("#regions", regionCount()) |
| .add("#devices", deviceLookup.size()) |
| .add("#hosts", hostLookup.size()) |
| .add("#dev-links", devLinkLookup.size()) |
| .add("#edge-links", edgeLinkLookup.size()) |
| .add("#synth-links", synthLinks.size()) |
| .toString(); |
| } |
| |
| @Override |
| public String idAsString() { |
| return DEFAULT_TOPOLOGY_ID; |
| } |
| |
| /** |
| * Clears the topology state; that is, drops all regions, devices, hosts, |
| * links, and cluster members. |
| */ |
| public void clear() { |
| log.debug("clearing topology model"); |
| cnodeLookup.clear(); |
| regionLookup.clear(); |
| deviceLookup.clear(); |
| hostLookup.clear(); |
| devLinkLookup.clear(); |
| edgeLinkLookup.clear(); |
| |
| synthLinks.clear(); |
| |
| nullRegion.destroy(); |
| } |
| |
| |
| /** |
| * Returns all the cluster members, sorted by their ID. |
| * |
| * @return all cluster members |
| */ |
| public List<UiClusterMember> allClusterMembers() { |
| List<UiClusterMember> members = new ArrayList<>(cnodeLookup.values()); |
| Collections.sort(members, CLUSTER_MEMBER_COMPARATOR); |
| return members; |
| } |
| |
| /** |
| * Returns the cluster member with the given identifier, or null if no |
| * such member exists. |
| * |
| * @param id cluster node identifier |
| * @return corresponding UI cluster member |
| */ |
| public UiClusterMember findClusterMember(NodeId id) { |
| return cnodeLookup.get(id); |
| } |
| |
| /** |
| * Adds the given cluster member to the topology model. |
| * |
| * @param member cluster member to add |
| */ |
| public void add(UiClusterMember member) { |
| cnodeLookup.put(member.id(), member); |
| } |
| |
| /** |
| * Removes the given cluster member from the topology model. |
| * |
| * @param member cluster member to remove |
| */ |
| public void remove(UiClusterMember member) { |
| UiClusterMember m = cnodeLookup.remove(member.id()); |
| if (m != null) { |
| m.destroy(); |
| } |
| } |
| |
| /** |
| * Returns the number of members in the cluster. |
| * |
| * @return number of cluster members |
| */ |
| public int clusterMemberCount() { |
| return cnodeLookup.size(); |
| } |
| |
| |
| /** |
| * Returns all regions in the model (except the |
| * {@link #nullRegion() null region}). |
| * |
| * @return all regions |
| */ |
| public Set<UiRegion> allRegions() { |
| return new HashSet<>(regionLookup.values()); |
| } |
| |
| /** |
| * Returns a reference to the null-region. That is, the container for |
| * devices, hosts, and links that belong to no region. |
| * |
| * @return the null-region |
| */ |
| public UiRegion nullRegion() { |
| return nullRegion; |
| } |
| |
| /** |
| * Returns the region with the specified identifier, or null if |
| * no such region exists. |
| * |
| * @param id region identifier |
| * @return corresponding UI region |
| */ |
| public UiRegion findRegion(RegionId id) { |
| return UiRegion.NULL_ID.equals(id) ? nullRegion() : regionLookup.get(id); |
| } |
| |
| /** |
| * Adds the given region to the topology model. |
| * |
| * @param uiRegion region to add |
| */ |
| public void add(UiRegion uiRegion) { |
| regionLookup.put(uiRegion.id(), uiRegion); |
| } |
| |
| /** |
| * Removes the given region from the topology model. |
| * |
| * @param uiRegion region to remove |
| */ |
| public void remove(UiRegion uiRegion) { |
| UiRegion r = regionLookup.remove(uiRegion.id()); |
| if (r != null) { |
| r.destroy(); |
| } |
| } |
| |
| /** |
| * Returns the number of regions configured in the topology. |
| * |
| * @return number of regions |
| */ |
| public int regionCount() { |
| return regionLookup.size(); |
| } |
| |
| /** |
| * Returns all devices in the model. |
| * |
| * @return all devices |
| */ |
| public Set<UiDevice> allDevices() { |
| return new HashSet<>(deviceLookup.values()); |
| } |
| |
| /** |
| * Returns the device with the specified identifier, or null if |
| * no such device exists. |
| * |
| * @param id device identifier |
| * @return corresponding UI device |
| */ |
| public UiDevice findDevice(DeviceId id) { |
| return deviceLookup.get(id); |
| } |
| |
| /** |
| * Adds the given device to the topology model. |
| * |
| * @param uiDevice device to add |
| */ |
| public void add(UiDevice uiDevice) { |
| deviceLookup.put(uiDevice.id(), uiDevice); |
| } |
| |
| /** |
| * Removes the given device from the topology model. |
| * |
| * @param uiDevice device to remove |
| */ |
| public void remove(UiDevice uiDevice) { |
| UiDevice d = deviceLookup.remove(uiDevice.id()); |
| if (d != null) { |
| d.destroy(); |
| } |
| } |
| |
| /** |
| * Returns the number of devices configured in the topology. |
| * |
| * @return number of devices |
| */ |
| public int deviceCount() { |
| return deviceLookup.size(); |
| } |
| |
| |
| /** |
| * Returns all device links in the model. |
| * |
| * @return all device links |
| */ |
| public Set<UiDeviceLink> allDeviceLinks() { |
| return new HashSet<>(devLinkLookup.values()); |
| } |
| |
| /** |
| * Returns the device link with the specified identifier, or null if no |
| * such link exists. |
| * |
| * @param id the canonicalized link identifier |
| * @return corresponding UI device link |
| */ |
| public UiDeviceLink findDeviceLink(UiLinkId id) { |
| return devLinkLookup.get(id); |
| } |
| |
| /** |
| * Returns the edge link with the specified identifier, or null if no |
| * such link exists. |
| * |
| * @param id the canonicalized link identifier |
| * @return corresponding UI edge link |
| */ |
| public UiEdgeLink findEdgeLink(UiLinkId id) { |
| return edgeLinkLookup.get(id); |
| } |
| |
| /** |
| * Adds the given UI device link to the topology model. |
| * |
| * @param uiDeviceLink link to add |
| */ |
| 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(); |
| } |
| } |
| |
| /** |
| * Removes the given UI edge link from the model. |
| * |
| * @param uiEdgeLink link to remove |
| */ |
| 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(); |
| } |
| |
| /** |
| * Returns all hosts in the model. |
| * |
| * @return all hosts |
| */ |
| public Set<UiHost> allHosts() { |
| return new HashSet<>(hostLookup.values()); |
| } |
| |
| /** |
| * Returns the host with the specified identifier, or null if no such |
| * host exists. |
| * |
| * @param id host identifier |
| * @return corresponding UI host |
| */ |
| public UiHost findHost(HostId id) { |
| return hostLookup.get(id); |
| } |
| |
| /** |
| * Adds the given host to the topology model. |
| * |
| * @param uiHost host to add |
| */ |
| public void add(UiHost uiHost) { |
| hostLookup.put(uiHost.id(), uiHost); |
| } |
| |
| /** |
| * Removes the given host from the topology model. |
| * |
| * @param uiHost host to remove |
| */ |
| public void remove(UiHost uiHost) { |
| UiHost h = hostLookup.remove(uiHost.id()); |
| if (h != null) { |
| h.destroy(); |
| } |
| } |
| |
| /** |
| * Returns the number of hosts configured in the topology. |
| * |
| * @return number of hosts |
| */ |
| public int hostCount() { |
| return hostLookup.size(); |
| } |
| |
| |
| // == |
| // package private methods for supporting linkage amongst topology entities |
| // == |
| |
| /** |
| * Returns the set of UI devices with the given identifiers. |
| * |
| * @param deviceIds device identifiers |
| * @return set of matching UI device instances |
| */ |
| Set<UiDevice> deviceSet(Set<DeviceId> deviceIds) { |
| Set<UiDevice> uiDevices = new HashSet<>(); |
| for (DeviceId id : deviceIds) { |
| UiDevice d = deviceLookup.get(id); |
| if (d != null) { |
| uiDevices.add(d); |
| } else { |
| log.warn(E_UNMAPPED, "device", id); |
| } |
| } |
| return uiDevices; |
| } |
| |
| /** |
| * Returns the set of UI hosts with the given identifiers. |
| * |
| * @param hostIds host identifiers |
| * @return set of matching UI host instances |
| */ |
| Set<UiHost> hostSet(Set<HostId> hostIds) { |
| Set<UiHost> uiHosts = new HashSet<>(); |
| for (HostId id : hostIds) { |
| UiHost h = hostLookup.get(id); |
| if (h != null) { |
| uiHosts.add(h); |
| } else { |
| log.warn(E_UNMAPPED, "host", id); |
| } |
| } |
| return uiHosts; |
| } |
| |
| /** |
| * Returns the set of UI device links with the given identifiers. |
| * |
| * @param uiLinkIds link identifiers |
| * @return set of matching UI device link instances |
| */ |
| Set<UiDeviceLink> linkSet(Set<UiLinkId> uiLinkIds) { |
| Set<UiDeviceLink> result = new HashSet<>(); |
| for (UiLinkId id : uiLinkIds) { |
| UiDeviceLink link = devLinkLookup.get(id); |
| if (link != null) { |
| result.add(link); |
| } else { |
| log.warn(E_UNMAPPED, "device link", id); |
| } |
| } |
| 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); |
| }); |
| |
| slinks.addAll(wrapHostLinks(nullRegion())); |
| for (UiRegion r: allRegions()) { |
| slinks.addAll(wrapHostLinks(r)); |
| } |
| |
| synthLinks.clear(); |
| synthLinks.addAll(slinks); |
| } |
| |
| private Set<UiSynthLink> wrapHostLinks(UiRegion region) { |
| RegionId regionId = region.id(); |
| return region.hosts().stream().map(h -> wrapHostLink(regionId, h)) |
| .collect(Collectors.toSet()); |
| } |
| |
| private UiSynthLink wrapHostLink(RegionId regionId, UiHost host) { |
| UiEdgeLink elink = new UiEdgeLink(this, host.edgeLinkId()); |
| return new UiSynthLink(regionId, elink); |
| } |
| |
| 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(); |
| } |
| |
| /** |
| * Returns a detailed (multi-line) string showing the contents of the |
| * topology. |
| * |
| * @return detailed string |
| */ |
| public String dumpString() { |
| StringBuilder sb = new StringBuilder("Topology:").append(EOL); |
| |
| sb.append(INDENT_1).append("Cluster Members").append(EOL); |
| for (UiClusterMember m : cnodeLookup.values()) { |
| sb.append(INDENT_2).append(m).append(EOL); |
| } |
| |
| sb.append(INDENT_1).append("Regions").append(EOL); |
| for (UiRegion r : regionLookup.values()) { |
| sb.append(INDENT_2).append(r).append(EOL); |
| } |
| |
| sb.append(INDENT_1).append("Devices").append(EOL); |
| for (UiDevice d : deviceLookup.values()) { |
| sb.append(INDENT_2).append(d).append(EOL); |
| } |
| |
| sb.append(INDENT_1).append("Hosts").append(EOL); |
| for (UiHost h : hostLookup.values()) { |
| sb.append(INDENT_2).append(h).append(EOL); |
| } |
| |
| 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(); |
| } |
| } |