Simon Hunt
Committed by Gerrit Code Review

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
Showing 21 changed files with 1502 additions and 308 deletions
/*
* 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.net.DeviceId;
import org.onosproject.net.Link;
import org.onosproject.net.PortNumber;
/**
* Represents a link between two devices; that is, an infrastructure link.
*/
public class UiDeviceLink extends UiLink {
private static final String E_UNASSOC =
"backing link not associated with this UI device link: ";
// devices and ports at either end of this link
private DeviceId deviceA;
private DeviceId deviceB;
private PortNumber portA;
private PortNumber portB;
// two unidirectional links underlying this link...
private Link linkAtoB;
private Link linkBtoA;
/**
* Creates a device to device UI link.
*
* @param topology parent topology
* @param id canonicalized link identifier
*/
public UiDeviceLink(UiTopology topology, UiLinkId id) {
super(topology, id);
}
@Override
public String endPointA() {
return deviceA + UiLinkId.ID_PORT_DELIMITER + portA;
}
@Override
public String endPointB() {
return deviceB + UiLinkId.ID_PORT_DELIMITER + portB;
}
@Override
protected void destroy() {
deviceA = null;
deviceB = null;
portA = null;
portB = null;
linkAtoB = null;
linkBtoA = null;
}
/**
* Attaches the given backing link to this UI link. This method will
* throw an exception if this UI link is not representative of the
* supplied link.
*
* @param link backing link to attach
* @throws IllegalArgumentException if the link is not appropriate
*/
public void attachBackingLink(Link link) {
UiLinkId.Direction d = id.directionOf(link);
if (d == UiLinkId.Direction.A_TO_B) {
linkAtoB = link;
deviceA = link.src().deviceId();
portA = link.src().port();
deviceB = link.dst().deviceId();
portB = link.dst().port();
} else if (d == UiLinkId.Direction.B_TO_A) {
linkBtoA = link;
deviceB = link.src().deviceId();
portB = link.src().port();
deviceA = link.dst().deviceId();
portA = link.dst().port();
} else {
throw new IllegalArgumentException(E_UNASSOC + link);
}
}
/**
* Detaches the given backing link from this UI link, returning true if the
* reverse link is still attached, or false otherwise.
*
* @param link the backing link to detach
* @return true if other link still attached, false otherwise
* @throws IllegalArgumentException if the link is not appropriate
*/
public boolean detachBackingLink(Link link) {
UiLinkId.Direction d = id.directionOf(link);
if (d == UiLinkId.Direction.A_TO_B) {
linkAtoB = null;
return linkBtoA != null;
}
if (d == UiLinkId.Direction.B_TO_A) {
linkBtoA = null;
return linkAtoB != null;
}
throw new IllegalArgumentException(E_UNASSOC + link);
}
/**
* Returns the identity of device A.
*
* @return device A ID
*/
public DeviceId deviceA() {
return deviceA;
}
/**
* Returns the port number of device A.
*
* @return port A
*/
public PortNumber portA() {
return portA;
}
/**
* Returns the identity of device B.
*
* @return device B ID
*/
public DeviceId deviceB() {
return deviceB;
}
/**
* Returns the port number of device B.
*
* @return port B
*/
public PortNumber portB() {
return portB;
}
/**
* Returns backing link from A to B.
*
* @return backing link A to B
*/
public Link linkAtoB() {
return linkAtoB;
}
/**
* Returns backing link from B to A.
*
* @return backing link B to A
*/
public Link linkBtoA() {
return linkBtoA;
}
}
/*
* 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.net.DeviceId;
import org.onosproject.net.EdgeLink;
import org.onosproject.net.PortNumber;
/**
* Designates a link between a device and a host; that is, an edge link.
*/
public class UiEdgeLink extends UiLink {
private static final String E_UNASSOC =
"backing link not associated with this UI edge link: ";
// private (synthetic) host link
private DeviceId edgeDevice;
private PortNumber edgePort;
private EdgeLink edgeLink;
/**
* Creates a UI link.
*
* @param topology parent topology
* @param id canonicalized link identifier
*/
public UiEdgeLink(UiTopology topology, UiLinkId id) {
super(topology, id);
}
@Override
public String endPointA() {
return edgeLink.hostId().toString();
}
@Override
public String endPointB() {
return edgeDevice + UiLinkId.ID_PORT_DELIMITER + edgePort;
}
@Override
protected void destroy() {
edgeDevice = null;
edgePort = null;
edgeLink = null;
}
/**
* Attaches the given edge link to this UI link. This method will
* throw an exception if this UI link is not representative of the
* supplied link.
*
* @param elink edge link to attach
* @throws IllegalArgumentException if the link is not appropriate
*/
public void attachEdgeLink(EdgeLink elink) {
UiLinkId.Direction d = id.directionOf(elink);
// Expected direction of edge links is A-to-B (Host to device)
// but checking not null is a sufficient test
if (d == null) {
throw new IllegalArgumentException(E_UNASSOC + elink);
}
edgeLink = elink;
edgeDevice = elink.hostLocation().deviceId();
edgePort = elink.hostLocation().port();
}
}
......@@ -16,17 +16,11 @@
package org.onosproject.ui.model.topo;
import org.onosproject.net.DeviceId;
import org.onosproject.net.EdgeLink;
import org.onosproject.net.Link;
import java.util.Set;
import static com.google.common.base.MoreObjects.toStringHelper;
/**
* Represents a link (line between two elements). This may have one of
* several forms:
* Represents a link (line between two elements). This may be one of
* several concrete subclasses:
* <ul>
* <li>
* An infrastructure link:
......@@ -38,17 +32,15 @@ import static com.google.common.base.MoreObjects.toStringHelper;
* </li>
* <li>
* An aggregation link:
* representing multiple underlying UI link instances.
* representing multiple underlying UI link instances, for example
* the link between two sub-regions in a region (layout).
* </li>
* </ul>
*/
public class UiLink extends UiElement {
private static final String E_UNASSOC =
"backing link not associated with this UI link: ";
public abstract class UiLink extends UiElement {
private final UiTopology topology;
private final UiLinkId id;
protected final UiTopology topology;
protected final UiLinkId id;
/**
* Creates a UI link.
......@@ -61,22 +53,6 @@ public class UiLink extends UiElement {
this.id = id;
}
// devices at either end of this link
private DeviceId deviceA;
private DeviceId deviceB;
// two unidirectional links underlying this link...
private Link linkAtoB;
private Link linkBtoA;
// ==OR== : private (synthetic) host link
private DeviceId edgeDevice;
private EdgeLink edgeLink;
// ==OR== : set of underlying UI links that this link aggregates
private Set<UiLink> children;
@Override
public String toString() {
return toStringHelper(this)
......@@ -84,19 +60,6 @@ public class UiLink extends UiElement {
.toString();
}
@Override
protected void destroy() {
deviceA = null;
deviceB = null;
linkAtoB = null;
linkBtoA = null;
edgeLink = null;
if (children != null) {
children.clear();
children = null;
}
}
/**
* Returns the canonicalized link identifier for this link.
*
......@@ -112,107 +75,25 @@ public class UiLink extends UiElement {
}
/**
* Attaches the given backing link to this UI link. This method will
* throw an exception if this UI link is not representative of the
* supplied link.
* Returns the implementing class name as the type of link.
*
* @param link backing link to attach
* @throws IllegalArgumentException if the link is not appropriate
* @return link type
*/
public void attachBackingLink(Link link) {
UiLinkId.Direction d = id.directionOf(link);
if (d == UiLinkId.Direction.A_TO_B) {
linkAtoB = link;
deviceA = link.src().deviceId();
deviceB = link.dst().deviceId();
} else if (d == UiLinkId.Direction.B_TO_A) {
linkBtoA = link;
deviceB = link.src().deviceId();
deviceA = link.dst().deviceId();
} else {
throw new IllegalArgumentException(E_UNASSOC + link);
}
public String type() {
return getClass().getSimpleName();
}
/**
* Detaches the given backing link from this UI link, returning true if the
* reverse link is still attached, or false otherwise.
* Returns the identifier of end-point A in string form.
*
* @param link the backing link to detach
* @return true if other link still attached, false otherwise
* @throws IllegalArgumentException if the link is not appropriate
* @return end point A identifier
*/
public boolean detachBackingLink(Link link) {
UiLinkId.Direction d = id.directionOf(link);
if (d == UiLinkId.Direction.A_TO_B) {
linkAtoB = null;
return linkBtoA != null;
}
if (d == UiLinkId.Direction.B_TO_A) {
linkBtoA = null;
return linkAtoB != null;
}
throw new IllegalArgumentException(E_UNASSOC + link);
}
public abstract String endPointA();
/**
* Attaches the given edge link to this UI link. This method will
* throw an exception if this UI link is not representative of the
* supplied link.
* Returns the identifier of end-point B in string form.
*
* @param elink edge link to attach
* @throws IllegalArgumentException if the link is not appropriate
* @return end point B identifier
*/
public void attachEdgeLink(EdgeLink elink) {
UiLinkId.Direction d = id.directionOf(elink);
// Expected direction of edge links is A-to-B (Host to device)
// but checking not null is sufficient
if (d == null) {
throw new IllegalArgumentException(E_UNASSOC + elink);
}
edgeLink = elink;
edgeDevice = elink.hostLocation().deviceId();
}
/**
* Returns the identity of device A.
*
* @return device A ID
*/
public DeviceId deviceA() {
return deviceA;
}
/**
* Returns the identity of device B.
*
* @return device B ID
*/
public DeviceId deviceB() {
return deviceB;
}
/**
* Returns backing link from A to B.
*
* @return backing link A to B
*/
public Link linkAtoB() {
return linkAtoB;
}
/**
* Returns backing link from B to A.
*
* @return backing link B to A
*/
public Link linkBtoA() {
return linkBtoA;
}
public abstract String endPointB();
}
......
......@@ -17,15 +17,30 @@
package org.onosproject.ui.model.topo;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.ElementId;
import org.onosproject.net.Link;
import org.onosproject.net.PortNumber;
import org.onosproject.net.region.RegionId;
import java.util.Comparator;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A canonical representation of an identifier for {@link UiLink}s.
*/
public final class UiLinkId {
private static final String E_PORT_NULL = "Port number cannot be null";
private static final String E_DEVICE_ID_NULL = "Device ID cannot be null";
private static final String E_REGION_ID_NULL = "Region ID cannot be null";
private static final String E_IDENTICAL = "Region IDs cannot be same";
private static final Comparator<RegionId> REGION_ID_COMPARATOR =
(o1, o2) -> o1.toString().compareTo(o2.toString());
/**
* Designates the directionality of an underlying (uni-directional) link.
*/
......@@ -34,12 +49,15 @@ public final class UiLinkId {
B_TO_A
}
private static final String CP_DELIMITER = "~";
private static final String ID_PORT_DELIMITER = "/";
static final String CP_DELIMITER = "~";
static final String ID_PORT_DELIMITER = "/";
private final ElementId idA;
private final RegionId regionA;
private final ElementId elementA;
private final PortNumber portA;
private final ElementId idB;
private final RegionId regionB;
private final ElementId elementB;
private final PortNumber portB;
private final String idStr;
......@@ -50,37 +68,81 @@ public final class UiLinkId {
* which is invariant to whether A or B is source or destination of the
* underlying link.
*
* @param a first element ID
* @param a first element ID
* @param pa first element port
* @param b second element ID
* @param b second element ID
* @param pb second element port
*/
private UiLinkId(ElementId a, PortNumber pa, ElementId b, PortNumber pb) {
idA = a;
elementA = a;
portA = pa;
idB = b;
elementB = b;
portB = pb;
regionA = null;
regionB = null;
idStr = a + ID_PORT_DELIMITER + pa + CP_DELIMITER +
b + ID_PORT_DELIMITER + pb;
}
/**
* Creates a UI link identifier. It is expected that A comes before B when
* the two identifiers are naturally sorted.
*
* @param a first region ID
* @param b second region ID
*/
private UiLinkId(RegionId a, RegionId b) {
regionA = a;
regionB = b;
elementA = null;
elementB = null;
portA = null;
portB = null;
idStr = a + CP_DELIMITER + b;
}
/**
* Creates a UI link identifier, with region at one end and a device/port
* at the other.
*
* @param r region ID
* @param d device ID
* @param p port number
*/
private UiLinkId(RegionId r, DeviceId d, PortNumber p) {
regionA = r;
elementB = d;
portB = p;
regionB = null;
elementA = null;
portA = null;
idStr = r + CP_DELIMITER + elementB + ID_PORT_DELIMITER + portB;
}
@Override
public String toString() {
return idStr;
}
/**
* Returns the identifier of the first element.
* Returns the identifier of the first element. Note that the returned
* value will be null if this identifier is for a region-region link.
*
* @return first element identity
*/
public ElementId elementA() {
return idA;
return elementA;
}
/**
* Returns the port of the first element.
* Returns the port of the first element. Note that the returned
* value will be null if this identifier is for a region-region link.
*
* @return first element port
*/
......@@ -89,16 +151,18 @@ public final class UiLinkId {
}
/**
* Returns the identifier of the second element.
* Returns the identifier of the second element. Note that the returned
* value will be null if this identifier is for a region-region link.
*
* @return second element identity
*/
public ElementId elementB() {
return idB;
return elementB;
}
/**
* Returns the port of the second element.
* Returns the port of the second element. Note that the returned
* value will be null if this identifier is for a region-region link.
*
* @return second element port
*/
......@@ -106,6 +170,28 @@ public final class UiLinkId {
return portB;
}
/**
* Returns the identity of the first region. Note that the returned value
* will be null if this identifier is for a device-device or device-host
* link.
*
* @return first region ID
*/
public RegionId regionA() {
return regionA;
}
/**
* Returns the identity of the second region. Note that the returned value
* will be null if this identifier is for a device-device or device-host
* link.
*
* @return second region ID
*/
public RegionId regionB() {
return regionB;
}
@Override
public boolean equals(Object o) {
if (this == o) {
......@@ -134,8 +220,8 @@ public final class UiLinkId {
Direction directionOf(Link link) {
ConnectPoint src = link.src();
ElementId srcId = src.elementId();
return idA.equals(srcId) ? Direction.A_TO_B
: idB.equals(srcId) ? Direction.B_TO_A
return elementA.equals(srcId) ? Direction.A_TO_B
: elementB.equals(srcId) ? Direction.B_TO_A
: null;
}
......@@ -161,4 +247,42 @@ public final class UiLinkId {
return comp <= 0 ? new UiLinkId(srcId, src.port(), dstId, dst.port())
: new UiLinkId(dstId, dst.port(), srcId, src.port());
}
/**
* Generates the canonical link identifier for a link between the
* specified region nodes.
*
* @param one the first region ID
* @param two the second region ID
* @return link identifier
* @throws NullPointerException if any of the required fields are null
* @throws IllegalArgumentException if the identifiers are identical
*/
public static UiLinkId uiLinkId(RegionId one, RegionId two) {
checkNotNull(one, E_REGION_ID_NULL);
checkNotNull(two, E_REGION_ID_NULL);
checkArgument(!one.equals(two), E_IDENTICAL);
boolean flip = REGION_ID_COMPARATOR.compare(one, two) > 0;
return flip ? new UiLinkId(two, one) : new UiLinkId(one, two);
}
/**
* Generates the canonical link identifier for a link between the specified
* region and device/port.
*
* @param regionId region ID
* @param deviceId device ID
* @param portNumber port number
* @return link identifier
* @throws NullPointerException if any of the required fields are null
*/
public static UiLinkId uiLinkId(RegionId regionId, DeviceId deviceId,
PortNumber portNumber) {
checkNotNull(regionId, E_REGION_ID_NULL);
checkNotNull(deviceId, E_DEVICE_ID_NULL);
checkNotNull(portNumber, E_PORT_NULL);
return new UiLinkId(regionId, deviceId, portNumber);
}
}
......
......@@ -53,7 +53,6 @@ public class UiRegion extends UiNode {
// loose bindings to things in this region
private final Set<DeviceId> deviceIds = new HashSet<>();
private final Set<HostId> hostIds = new HashSet<>();
private final Set<UiLinkId> uiLinkIds = new HashSet<>();
private final List<String> layerOrder = new ArrayList<>();
......@@ -84,7 +83,6 @@ public class UiRegion extends UiNode {
protected void destroy() {
deviceIds.clear();
hostIds.clear();
uiLinkIds.clear();
}
/**
......@@ -135,6 +133,15 @@ public class UiRegion extends UiNode {
}
/**
* Returns the UI region that is the parent of this region.
*
* @return the parent region
*/
public UiRegion parentRegion() {
return topology.findRegion(parent);
}
/**
* Sets the parent ID for this region.
*
* @param parentId parent ID
......@@ -192,7 +199,6 @@ public class UiRegion extends UiNode {
.add("kids", kids)
.add("devices", deviceIds)
.add("#hosts", hostIds.size())
.add("#links", uiLinkIds.size())
.toString();
}
......@@ -252,24 +258,6 @@ public class UiRegion extends UiNode {
}
/**
* Returns the set of link identifiers for this region.
*
* @return link identifiers for this region
*/
public Set<UiLinkId> linkIds() {
return ImmutableSet.copyOf(uiLinkIds);
}
/**
* Returns the links in this region.
*
* @return the links in this region
*/
public Set<UiLink> links() {
return topology.linkSet(uiLinkIds);
}
/**
* Returns the order in which layers should be rendered. Lower layers
* come earlier in the list. For example, to indicate that nodes in the
* optical layer should be rendered "below" nodes in the packet layer,
......
/*
* 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.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.region.RegionId;
/**
* Designates a link between a region node and a device.
*/
public class UiRegionDeviceLink extends UiLink {
private static final String E_NOT_REGION_DEVICE_ID =
"UI link identifier not region to device";
// private (synthetic) region-device link
private final RegionId region;
private final DeviceId device;
private final PortNumber port;
/**
* Creates a region to device UI link. Note that it is expected that the
* link identifier is one that has a region ID at one end, and a device
* ID at the other
*
* @param topology parent topology
* @param id canonicalized link identifier
* @throws IllegalArgumentException if the link ID is not region-region
*/
public UiRegionDeviceLink(UiTopology topology, UiLinkId id) {
super(topology, id);
region = id.regionA();
device = (DeviceId) id.elementB();
port = id.portB();
if (region == null || device == null || port == null) {
throw new IllegalArgumentException(E_NOT_REGION_DEVICE_ID);
}
}
@Override
public String endPointA() {
return region.id();
}
@Override
public String endPointB() {
return device + UiLinkId.ID_PORT_DELIMITER + port;
}
/**
* Returns the identity of the region.
*
* @return region ID
*/
public RegionId region() {
return region;
}
/**
* Returns the identity of the device.
*
* @return device ID
*/
public DeviceId device() {
return device;
}
/**
* Returns the identity of the device port.
*
* @return device port number
*/
public PortNumber port() {
return port;
}
}
/*
* 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.net.region.RegionId;
/**
* Designates a link between two region nodes.
*/
public class UiRegionLink extends UiLink {
private static final String E_NOT_REGION_ID =
"UI link identifier not region to region";
// private (synthetic) region - region link
private final RegionId regionA;
private final RegionId regionB;
/**
* Creates a region to region UI link. Note that it is expected that the
* link identifier is one that has region IDs as source and destination.
*
* @param topology parent topology
* @param id canonicalized link identifier
* @throws IllegalArgumentException if the link ID is not region-region
*/
public UiRegionLink(UiTopology topology, UiLinkId id) {
super(topology, id);
regionA = id.regionA();
regionB = id.regionB();
if (regionA == null || regionB == null) {
throw new IllegalArgumentException(E_NOT_REGION_ID);
}
}
@Override
public String endPointA() {
return regionA.id();
}
@Override
public String endPointB() {
return regionB.id();
}
/**
* Returns the identity of the first region.
*
* @return first region ID
*/
public RegionId regionA() {
return regionA;
}
/**
* Returns the identity of the second region.
*
* @return second region ID
*/
public RegionId regionB() {
return regionB;
}
}
/*
* 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.net.region.RegionId;
import static com.google.common.base.MoreObjects.toStringHelper;
/**
* A synthetic link that encapsulates a UiLink instance and the region to
* which it belongs.
*/
public class UiSynthLink {
private final RegionId regionId;
private final UiLink link;
/**
* Constructs a synthetic link with the given parameters.
*
* @param regionId the region to which the link belongs
* @param link the link instance
*/
public UiSynthLink(RegionId regionId, UiLink link) {
this.regionId = regionId;
this.link = link;
}
@Override
public String toString() {
return toStringHelper(this)
.add("region", regionId)
.add("link", link)
.toString();
}
/**
* Returns the region identifier.
*
* @return the region ID
*/
public RegionId regionId() {
return regionId;
}
/**
* Returns the link.
*
* @return the link
*/
public UiLink link() {
return link;
}
}
......@@ -19,6 +19,7 @@ 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;
......@@ -30,9 +31,12 @@ 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.
......@@ -59,7 +63,11 @@ public class UiTopology extends UiElement {
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 @@ public class UiTopology extends UiElement {
.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 @@ public class UiTopology extends UiElement {
regionLookup.clear();
deviceLookup.clear();
hostLookup.clear();
linkLookup.clear();
devLinkLookup.clear();
edgeLinkLookup.clear();
synthLinks.clear();
nullRegion.destroy();
}
......@@ -261,54 +274,96 @@ public class UiTopology extends UiElement {
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);
}
/**
* 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 link from the model.
* Removes the given UI edge link from the model.
*
* @param uiLink link to remove
* @param uiEdgeLink link to remove
*/
public void remove(UiLink uiLink) {
UiLink link = linkLookup.remove(uiLink.id());
public void remove(UiEdgeLink uiEdgeLink) {
UiEdgeLink link = edgeLinkLookup.remove(uiEdgeLink.id());
if (link != null) {
link.destroy();
}
}
/**
* Returns the number of links configured in the topology.
* 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 links
* @return number of edge links
*/
public int linkCount() {
return linkLookup.size();
public int edgeLinkCount() {
return edgeLinkLookup.size();
}
/**
......@@ -405,22 +460,198 @@ public class UiTopology extends UiElement {
}
/**
* 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 @@ public class UiTopology extends UiElement {
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();
}
}
......
/*
* 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.junit.Test;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultEdgeLink;
import org.onosproject.net.DeviceId;
import org.onosproject.net.EdgeLink;
import org.onosproject.net.PortNumber;
import org.onosproject.ui.model.AbstractUiModelTest;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link UiEdgeLink}.
*/
public class UiEdgeLinkTest extends AbstractUiModelTest {
private static final String PHANTOM_HOST_ID = "00:00:00:00:00:00/None";
private static final String D1_P8 = "dev-1/8";
private static final DeviceId DEV = DeviceId.deviceId("dev-1");
private static final PortNumber P8 = PortNumber.portNumber(8);
private static final ConnectPoint CP = new ConnectPoint(DEV, P8);
private static final EdgeLink EDGE_LINK =
DefaultEdgeLink.createEdgeLink(CP, true);
@Test
public void basic() {
title("basic");
UiLinkId id = UiLinkId.uiLinkId(EDGE_LINK);
UiEdgeLink link = new UiEdgeLink(null, id);
link.attachEdgeLink(EDGE_LINK);
print(link);
print(link.endPointA());
print(link.endPointB());
assertEquals("bad end point A", PHANTOM_HOST_ID, link.endPointA());
assertEquals("bad end point B", D1_P8, link.endPointB());
}
}
......@@ -17,17 +17,23 @@
package org.onosproject.ui.model.topo;
import org.junit.Test;
import org.onlab.packet.MacAddress;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultLink;
import org.onosproject.net.DeviceId;
import org.onosproject.net.HostId;
import org.onosproject.net.Link;
import org.onosproject.net.PortNumber;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.net.region.RegionId;
import org.onosproject.ui.model.AbstractUiModelTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.onosproject.net.DeviceId.deviceId;
import static org.onosproject.net.HostId.hostId;
import static org.onosproject.net.PortNumber.P0;
import static org.onosproject.net.PortNumber.portNumber;
/**
......@@ -35,8 +41,15 @@ import static org.onosproject.net.PortNumber.portNumber;
*/
public class UiLinkIdTest extends AbstractUiModelTest {
private static final RegionId REG_1 = RegionId.regionId("Region-1");
private static final RegionId REG_2 = RegionId.regionId("Region-2");
private static final MacAddress MAC_A = MacAddress.valueOf(0x123456L);
private static final HostId HOST_A = hostId(MAC_A);
private static final DeviceId DEV_X = deviceId("device-X");
private static final DeviceId DEV_Y = deviceId("device-Y");
private static final PortNumber P1 = portNumber(1);
private static final PortNumber P2 = portNumber(2);
private static final PortNumber P3 = portNumber(3);
......@@ -45,6 +58,8 @@ public class UiLinkIdTest extends AbstractUiModelTest {
private static final ConnectPoint CP_Y2 = new ConnectPoint(DEV_Y, P2);
private static final ConnectPoint CP_Y3 = new ConnectPoint(DEV_Y, P3);
private static final ConnectPoint CP_HA = new ConnectPoint(HOST_A, P0);
private static final Link LINK_X1_TO_Y2 = DefaultLink.builder()
.providerId(ProviderId.NONE)
.src(CP_X1)
......@@ -66,6 +81,12 @@ public class UiLinkIdTest extends AbstractUiModelTest {
.type(Link.Type.DIRECT)
.build();
private static final Link LINK_HA_TO_X1 = DefaultLink.builder()
.providerId(ProviderId.NONE)
.src(CP_HA)
.dst(CP_X1)
.type(Link.Type.EDGE)
.build();
@Test
public void canonical() {
......@@ -86,4 +107,61 @@ public class UiLinkIdTest extends AbstractUiModelTest {
print("link other: %s", other);
assertNotEquals("equiv?", one, other);
}
@Test
public void edgeLink() {
title("edgeLink");
UiLinkId id = UiLinkId.uiLinkId(LINK_HA_TO_X1);
print("link: %s", id);
assertEquals("wrong port A", P0, id.portA());
assertEquals("wrong element A", HOST_A, id.elementA());
assertEquals("wrong port B", P1, id.portB());
assertEquals("wrong element B", DEV_X, id.elementB());
assertNull("region A?", id.regionA());
assertNull("region B?", id.regionB());
}
@Test
public void deviceLink() {
title("deviceLink");
UiLinkId id = UiLinkId.uiLinkId(LINK_X1_TO_Y2);
print("link: %s", id);
assertEquals("wrong port A", P1, id.portA());
assertEquals("wrong element A", DEV_X, id.elementA());
assertEquals("wrong port B", P2, id.portB());
assertEquals("wrong element B", DEV_Y, id.elementB());
assertNull("region A?", id.regionA());
assertNull("region B?", id.regionB());
}
@Test
public void regionLink() {
title("regionLink");
UiLinkId idFirst = UiLinkId.uiLinkId(REG_1, REG_2);
UiLinkId idSecond = UiLinkId.uiLinkId(REG_2, REG_1);
print(" first: %s", idFirst);
print("second: %s", idSecond);
assertEquals("Not same ID", idFirst, idSecond);
}
@Test(expected = IllegalArgumentException.class)
public void identicalRegionBad() {
UiLinkId.uiLinkId(REG_1, REG_1);
}
@Test(expected = NullPointerException.class)
public void nullRegionBad() {
UiLinkId.uiLinkId(REG_1, (RegionId) null);
}
@Test
public void regionDeviceLink() {
title("regionDeviceLink");
UiLinkId id = UiLinkId.uiLinkId(REG_1, DEV_X, P1);
print("id: %s", id);
assertEquals("region ID", REG_1, id.regionA());
assertEquals("device ID", DEV_X, id.elementB());
assertEquals("port", P1, id.portB());
}
}
......
/*
* 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.junit.Test;
import org.onosproject.net.DeviceId;
import org.onosproject.net.region.RegionId;
import org.onosproject.ui.model.AbstractUiModelTest;
import static org.junit.Assert.assertEquals;
import static org.onosproject.net.DeviceId.deviceId;
import static org.onosproject.net.PortNumber.P0;
import static org.onosproject.net.region.RegionId.regionId;
/**
* Unit tests for {@link UiRegionDeviceLink}.
*/
public class UiRegionDeviceLinkTest extends AbstractUiModelTest {
private static final RegionId R1 = regionId("r1");
private static final DeviceId DEV_X = deviceId("device-X");
@Test
public void basic() {
title("basic");
UiLinkId id = UiLinkId.uiLinkId(R1, DEV_X, P0);
UiRegionDeviceLink link = new UiRegionDeviceLink(null, id);
print(link);
assertEquals("region", R1, link.region());
assertEquals("device", DEV_X, link.device());
assertEquals("port", P0, link.port());
}
}
/*
* 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.junit.Test;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultLink;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Link;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.net.region.RegionId;
import org.onosproject.ui.model.AbstractUiModelTest;
import static org.junit.Assert.assertEquals;
import static org.onosproject.net.DeviceId.deviceId;
import static org.onosproject.net.PortNumber.P0;
import static org.onosproject.net.region.RegionId.regionId;
/**
* Unit tests for {@link UiRegionLink}.
*/
public class UiRegionLinkTest extends AbstractUiModelTest {
private static final RegionId R1 = regionId("r1");
private static final RegionId R2 = regionId("r2");
private static final DeviceId DEV_X = deviceId("device-X");
private static final DeviceId DEV_Y = deviceId("device-Y");
private static final ConnectPoint CP_X = new ConnectPoint(DEV_X, P0);
private static final ConnectPoint CP_Y = new ConnectPoint(DEV_Y, P0);
private static final Link LINK_X_TO_Y = DefaultLink.builder()
.providerId(ProviderId.NONE)
.src(CP_X)
.dst(CP_Y)
.type(Link.Type.DIRECT)
.build();
@Test(expected = NullPointerException.class)
public void nullPointerRegion() {
title("nullPointerRegion");
new UiRegionLink(null, null);
}
@Test
public void regionToRegion() {
title("regionToRegion");
UiLinkId id = UiLinkId.uiLinkId(R1, R2);
UiRegionLink link = new UiRegionLink(null, id);
print("link: %s", link);
assertEquals("bad first region", R1, link.regionA());
assertEquals("bad second region", R2, link.regionB());
}
@Test(expected = IllegalArgumentException.class)
public void wrongLinkType() {
title("wrongLinkType");
UiLinkId id = UiLinkId.uiLinkId(LINK_X_TO_Y);
new UiRegionLink(null, id);
}
}
......@@ -16,20 +16,165 @@
package org.onosproject.ui.model.topo;
import org.junit.Before;
import org.junit.Test;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultLink;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Link;
import org.onosproject.net.PortNumber;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.net.region.RegionId;
import org.onosproject.ui.AbstractUiTest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.onosproject.net.DeviceId.deviceId;
import static org.onosproject.net.PortNumber.portNumber;
/**
* Unit tests for {@link UiTopology}.
*/
public class UiTopologyTest extends AbstractUiTest {
private static final DeviceId DEV_X = deviceId("dev-X");
private static final DeviceId DEV_Y = deviceId("dev-Y");
private static final PortNumber P1 = portNumber(1);
private static final PortNumber P2 = portNumber(2);
private static final String DEV_X_ID = "dev-x/1";
private static final String DEV_Y_ID = "dev-y/2";
private static final ConnectPoint CP_X1 = new ConnectPoint(DEV_X, P1);
private static final ConnectPoint CP_Y2 = new ConnectPoint(DEV_Y, P2);
private static final Link LINK_X1_TO_Y2 = DefaultLink.builder()
.providerId(ProviderId.NONE)
.src(CP_X1)
.dst(CP_Y2)
.type(Link.Type.DIRECT)
.build();
private static final UiLinkId DX1_DY2 = UiLinkId.uiLinkId(LINK_X1_TO_Y2);
private static final RegionId ROOT = UiRegion.NULL_ID;
private static final RegionId R1 = RegionId.regionId("R1");
private static final RegionId R2 = RegionId.regionId("R2");
private static final RegionId R3 = RegionId.regionId("R3");
private static final String DEV_LINK_CLASS = "UiDeviceLink";
private static final String REG_LINK_CLASS = "UiRegionLink";
private static final String REG_DEV_LINK_CLASS = "UiRegionDeviceLink";
private UiTopology topo;
private UiDeviceLink devLink;
private List<RegionId> xBranch;
private List<RegionId> yBranch;
private UiSynthLink synth;
@Before
public void setUp() {
topo = new UiTopology();
devLink = new UiDeviceLink(null, DX1_DY2);
devLink.attachBackingLink(LINK_X1_TO_Y2);
}
@Test
public void basic() {
title("basic");
topo = new UiTopology();
print(topo);
}
private List<RegionId> branch(RegionId... ids) {
List<RegionId> result = new ArrayList<>(ids.length);
Collections.addAll(result, ids);
return result;
}
private void verifySynth(RegionId id, String cls, String epA, String epB) {
synth = topo.makeSynthLink(devLink, xBranch, yBranch);
UiLink ulink = synth.link();
print(synth);
print("EpA{%s} EpB{%s}", ulink.endPointA(), ulink.endPointB());
assertEquals("wrong region", id, synth.regionId());
assertEquals("wrong link class", cls, ulink.type());
assertEquals("wrong EP A", epA, ulink.endPointA());
assertEquals("wrong EP B", epB, ulink.endPointB());
}
@Test
public void makeSynthDevToDevRoot() {
title("makeSynthDevToDevRoot");
xBranch = branch(ROOT);
yBranch = branch(ROOT);
verifySynth(ROOT, DEV_LINK_CLASS, DEV_X_ID, DEV_Y_ID);
}
@Test
public void makeSynthDevToDevR1() {
title("makeSynthDevToDevR1");
xBranch = branch(ROOT, R1);
yBranch = branch(ROOT, R1);
verifySynth(R1, DEV_LINK_CLASS, DEV_X_ID, DEV_Y_ID);
}
@Test
public void makeSynthDevToDevR2() {
title("makeSynthDevToDevR2");
xBranch = branch(ROOT, R1, R2);
yBranch = branch(ROOT, R1, R2);
verifySynth(R2, DEV_LINK_CLASS, DEV_X_ID, DEV_Y_ID);
}
@Test
public void makeSynthRegToRegRoot() {
title("makeSynthRegToRegRoot");
xBranch = branch(ROOT, R1);
yBranch = branch(ROOT, R2);
verifySynth(ROOT, REG_LINK_CLASS, R1.id(), R2.id());
}
@Test
public void makeSynthRegToRegR1() {
title("makeSynthRegToRegR1");
xBranch = branch(ROOT, R1, R2);
yBranch = branch(ROOT, R1, R3);
verifySynth(R1, REG_LINK_CLASS, R2.id(), R3.id());
}
@Test
public void makeSynthRegToDevRoot() {
title("makeSynthRegToDevRoot");
// Note: link is canonicalized to region--device order
xBranch = branch(ROOT);
yBranch = branch(ROOT, R1);
verifySynth(ROOT, REG_DEV_LINK_CLASS, R1.id(), DEV_X_ID);
xBranch = branch(ROOT, R1);
yBranch = branch(ROOT);
verifySynth(ROOT, REG_DEV_LINK_CLASS, R1.id(), DEV_Y_ID);
}
@Test
public void makeSynthRegToDevR3() {
title("makeSynthRegToDevR3");
// Note: link is canonicalized to region--device order
xBranch = branch(ROOT, R3);
yBranch = branch(ROOT, R3, R1);
verifySynth(R3, REG_DEV_LINK_CLASS, R1.id(), DEV_X_ID);
xBranch = branch(ROOT, R3, R1);
yBranch = branch(ROOT, R3);
verifySynth(R3, REG_DEV_LINK_CLASS, R1.id(), DEV_Y_ID);
}
}
......
......@@ -16,6 +16,7 @@
package org.onosproject.ui.impl.topo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
......@@ -39,6 +40,7 @@ import org.onosproject.ui.model.topo.UiHost;
import org.onosproject.ui.model.topo.UiLink;
import org.onosproject.ui.model.topo.UiNode;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiSynthLink;
import org.onosproject.ui.model.topo.UiTopoLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -180,9 +182,11 @@ class Topo2Jsonifier {
*
* @param region the region to transform to JSON
* @param subRegions the subregions within this region
* @param links the links within this region
* @return a JSON representation of the data
*/
ObjectNode region(UiRegion region, Set<UiRegion> subRegions) {
ObjectNode region(UiRegion region, Set<UiRegion> subRegions,
List<UiSynthLink> links) {
ObjectNode payload = objectNode();
if (region == null) {
payload.put("note", "no-region");
......@@ -193,14 +197,16 @@ class Topo2Jsonifier {
payload.set("subregions", jsonSubRegions(subRegions));
}
if (links != null) {
payload.set("links", jsonLinks(links));
}
List<String> layerTags = region.layerOrder();
List<Set<UiNode>> splitDevices = splitByLayer(layerTags, region.devices());
List<Set<UiNode>> splitHosts = splitByLayer(layerTags, region.hosts());
Set<UiLink> links = region.links();
payload.set("devices", jsonGrouped(splitDevices));
payload.set("hosts", jsonGrouped(splitHosts));
payload.set("links", jsonLinks(links));
payload.set("layerOrder", jsonStrings(layerTags));
return payload;
......@@ -208,24 +214,22 @@ class Topo2Jsonifier {
private ArrayNode jsonSubRegions(Set<UiRegion> subregions) {
ArrayNode kids = arrayNode();
if (subregions != null) {
subregions.forEach(s -> kids.add(jsonClosedRegion(s)));
}
subregions.forEach(s -> kids.add(jsonClosedRegion(s)));
return kids;
}
private JsonNode jsonLinks(List<UiSynthLink> links) {
ArrayNode synthLinks = arrayNode();
links.forEach(l -> synthLinks.add(json(l)));
return synthLinks;
}
private ArrayNode jsonStrings(List<String> strings) {
ArrayNode array = arrayNode();
strings.forEach(array::add);
return array;
}
private ArrayNode jsonLinks(Set<UiLink> links) {
ArrayNode result = arrayNode();
links.forEach(lnk -> result.add(json(lnk)));
return result;
}
private ArrayNode jsonGrouped(List<Set<UiNode>> groupedNodes) {
ArrayNode result = arrayNode();
groupedNodes.forEach(g -> {
......@@ -280,11 +284,13 @@ class Topo2Jsonifier {
// TODO: complete host details
}
private ObjectNode json(UiLink link) {
private ObjectNode json(UiSynthLink sLink) {
UiLink uLink = sLink.link();
return objectNode()
.put("id", link.idAsString());
// TODO: complete link details
.put("id", uLink.idAsString())
.put("epA", uLink.endPointA())
.put("epB", uLink.endPointB())
.put("type", uLink.type());
}
......@@ -305,7 +311,7 @@ class Topo2Jsonifier {
*/
public ArrayNode closedNodes(Set<UiNode> nodes) {
ArrayNode array = arrayNode();
for (UiNode node: nodes) {
for (UiNode node : nodes) {
if (node instanceof UiRegion) {
array.add(jsonClosedRegion((UiRegion) node));
} else if (node instanceof UiDevice) {
......@@ -361,20 +367,6 @@ class Topo2Jsonifier {
return array;
}
/**
* Returns a JSON array representation of a list of links.
*
* @param links the links
* @return a JSON representation of the links
*/
public ArrayNode links(Set<UiLink> links) {
ArrayNode array = arrayNode();
for (UiLink link : links) {
array.add(json(link));
}
return array;
}
// package-private for unit testing
List<Set<UiNode>> splitByLayer(List<String> layerTags,
Set<? extends UiNode> nodes) {
......
......@@ -26,6 +26,7 @@ import org.onosproject.ui.impl.UiWebSocket;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiNode;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiSynthLink;
import org.onosproject.ui.model.topo.UiTopoLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -125,7 +126,8 @@ public class Topo2ViewMessageHandler extends UiMessageHandler {
// (as well as layer-order hints)
UiRegion region = topoSession.getRegion(currentLayout);
Set<UiRegion> kids = topoSession.getSubRegions(currentLayout);
sendMessage(CURRENT_REGION, t2json.region(region, kids));
List<UiSynthLink> links = topoSession.getLinks(currentLayout);
sendMessage(CURRENT_REGION, t2json.region(region, kids, links));
// these are the regions/devices that are siblings to this region
Set<UiNode> peers = topoSession.getPeerNodes(currentLayout);
......@@ -133,6 +135,8 @@ public class Topo2ViewMessageHandler extends UiMessageHandler {
peersPayload.set("peers", t2json.closedNodes(peers));
sendMessage(PEER_REGIONS, peersPayload);
// TODO: send breadcrumb message
// finally, tell the UI that we are done : TODO review / delete??
sendMessage(TOPO_START_DONE, null);
......
......@@ -25,6 +25,7 @@ import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiNode;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiSynthLink;
import org.onosproject.ui.model.topo.UiTopoLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -214,6 +215,16 @@ public class UiTopoSession implements UiModelListener {
}
/**
* Returns the (synthetic) links of the region in the specified layout.
*
* @param layout the layout being viewed
* @return all links that are contained by this layout's region
*/
public List<UiSynthLink> getLinks(UiTopoLayout layout) {
return sharedModel.getSynthLinks(layout.regionId());
}
/**
* Refreshes the model's internal state.
*/
public void refreshModel() {
......
......@@ -29,6 +29,6 @@ public class ListLinks extends AbstractElementCommand {
@Override
protected void execute() {
UiSharedTopologyModel model = get(UiSharedTopologyModel.class);
sorted(model.getLinks()).forEach(l -> print("%s", l));
sorted(model.getDeviceLinks()).forEach(l -> print("%s", l));
}
}
......
......@@ -33,11 +33,13 @@ import org.onosproject.ui.UiTopoLayoutService;
import org.onosproject.ui.model.ServiceBundle;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiDevice;
import org.onosproject.ui.model.topo.UiDeviceLink;
import org.onosproject.ui.model.topo.UiEdgeLink;
import org.onosproject.ui.model.topo.UiElement;
import org.onosproject.ui.model.topo.UiHost;
import org.onosproject.ui.model.topo.UiLink;
import org.onosproject.ui.model.topo.UiLinkId;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiSynthLink;
import org.onosproject.ui.model.topo.UiTopoLayout;
import org.onosproject.ui.model.topo.UiTopoLayoutId;
import org.onosproject.ui.model.topo.UiTopology;
......@@ -101,7 +103,7 @@ class ModelCache {
loadClusterMembers();
loadRegions();
loadDevices();
loadLinks();
loadDeviceLinks();
loadHosts();
}
......@@ -334,66 +336,72 @@ class ModelCache {
}
// === LINKS
// === LINKS ===
private UiLink addNewLink(UiLinkId id) {
UiLink uiLink = new UiLink(uiTopology, id);
uiTopology.add(uiLink);
return uiLink;
private UiDeviceLink addNewDeviceLink(UiLinkId id) {
UiDeviceLink uiDeviceLink = new UiDeviceLink(uiTopology, id);
uiTopology.add(uiDeviceLink);
return uiDeviceLink;
}
private void updateLink(UiLink uiLink, Link link) {
uiLink.attachBackingLink(link);
private UiEdgeLink addNewEdgeLink(UiLinkId id) {
UiEdgeLink uiEdgeLink = new UiEdgeLink(uiTopology, id);
uiTopology.add(uiEdgeLink);
return uiEdgeLink;
}
private void loadLinks() {
private void updateDeviceLink(UiDeviceLink uiDeviceLink, Link link) {
uiDeviceLink.attachBackingLink(link);
}
private void loadDeviceLinks() {
for (Link link : services.link().getLinks()) {
UiLinkId id = uiLinkId(link);
UiLink uiLink = uiTopology.findLink(id);
if (uiLink == null) {
uiLink = addNewLink(id);
UiDeviceLink uiDeviceLink = uiTopology.findDeviceLink(id);
if (uiDeviceLink == null) {
uiDeviceLink = addNewDeviceLink(id);
}
updateLink(uiLink, link);
updateDeviceLink(uiDeviceLink, link);
}
}
// invoked from UiSharedTopologyModel link listener
void addOrUpdateLink(Link link) {
void addOrUpdateDeviceLink(Link link) {
UiLinkId id = uiLinkId(link);
UiLink uiLink = uiTopology.findLink(id);
if (uiLink == null) {
uiLink = addNewLink(id);
UiDeviceLink uiDeviceLink = uiTopology.findDeviceLink(id);
if (uiDeviceLink == null) {
uiDeviceLink = addNewDeviceLink(id);
}
updateLink(uiLink, link);
updateDeviceLink(uiDeviceLink, link);
postEvent(LINK_ADDED_OR_UPDATED, uiLink);
postEvent(LINK_ADDED_OR_UPDATED, uiDeviceLink);
}
// package private for unit test access
UiLink accessLink(UiLinkId id) {
return uiTopology.findLink(id);
UiDeviceLink accessDeviceLink(UiLinkId id) {
return uiTopology.findDeviceLink(id);
}
// invoked from UiSharedTopologyModel link listener
void removeLink(Link link) {
void removeDeviceLink(Link link) {
UiLinkId id = uiLinkId(link);
UiLink uiLink = uiTopology.findLink(id);
if (uiLink != null) {
boolean remaining = uiLink.detachBackingLink(link);
UiDeviceLink uiDeviceLink = uiTopology.findDeviceLink(id);
if (uiDeviceLink != null) {
boolean remaining = uiDeviceLink.detachBackingLink(link);
if (remaining) {
postEvent(LINK_ADDED_OR_UPDATED, uiLink);
postEvent(LINK_ADDED_OR_UPDATED, uiDeviceLink);
} else {
uiTopology.remove(uiLink);
postEvent(LINK_REMOVED, uiLink);
uiTopology.remove(uiDeviceLink);
postEvent(LINK_REMOVED, uiDeviceLink);
}
} else {
log.warn(E_NO_ELEMENT, "link", id);
log.warn(E_NO_ELEMENT, "Device link", id);
}
}
Set<UiLink> getAllLinks() {
return uiTopology.allLinks();
Set<UiDeviceLink> getAllDeviceLinks() {
return uiTopology.allDeviceLinks();
}
// === HOSTS
......@@ -411,20 +419,19 @@ class ModelCache {
host.setEdgeLinkId(elinkId);
// add synthesized edge link to the topology
UiLink edgeLink = addNewLink(elinkId);
UiEdgeLink edgeLink = addNewEdgeLink(elinkId);
edgeLink.attachEdgeLink(elink);
return host;
}
private void insertNewUiLink(UiLinkId id, EdgeLink e) {
UiLink newEdgeLink = addNewLink(id);
private void insertNewUiEdgeLink(UiLinkId id, EdgeLink e) {
UiEdgeLink newEdgeLink = addNewEdgeLink(id);
newEdgeLink.attachEdgeLink(e);
}
private void updateHost(UiHost uiHost, Host h) {
UiLink existing = uiTopology.findLink(uiHost.edgeLinkId());
UiEdgeLink existing = uiTopology.findEdgeLink(uiHost.edgeLinkId());
EdgeLink currentElink = synthesizeLink(h);
UiLinkId currentElinkId = uiLinkId(currentElink);
......@@ -432,7 +439,7 @@ class ModelCache {
if (existing != null) {
if (!currentElinkId.equals(existing.id())) {
// edge link has changed
insertNewUiLink(currentElinkId, currentElink);
insertNewUiEdgeLink(currentElinkId, currentElink);
uiHost.setEdgeLinkId(currentElinkId);
uiTopology.remove(existing);
......@@ -440,7 +447,7 @@ class ModelCache {
} else {
// no previously existing edge link
insertNewUiLink(currentElinkId, currentElink);
insertNewUiEdgeLink(currentElinkId, currentElink);
uiHost.setEdgeLinkId(currentElinkId);
}
......@@ -489,7 +496,7 @@ class ModelCache {
HostId id = host.id();
UiHost uiHost = uiTopology.findHost(id);
if (uiHost != null) {
UiLink edgeLink = uiTopology.findLink(uiHost.edgeLinkId());
UiEdgeLink edgeLink = uiTopology.findEdgeLink(uiHost.edgeLinkId());
uiTopology.remove(edgeLink);
uiTopology.remove(uiHost);
postEvent(HOST_REMOVED, uiHost);
......@@ -503,11 +510,17 @@ class ModelCache {
}
// === SYNTHETIC LINKS
List<UiSynthLink> getSynthLinks(RegionId regionId) {
return uiTopology.findSynthLinks(regionId);
}
/**
* Refreshes the internal state.
*/
public void refresh() {
// fix up internal linkages if they aren't correct
// fix up internal linkages to ensure they are correct
// make sure regions reflect layout containment hierarchy
fixupContainmentHierarchy(uiTopology.nullRegion());
......@@ -542,8 +555,13 @@ class ModelCache {
Set<DeviceId> leftOver = new HashSet<>(allDevices.size());
allDevices.forEach(d -> leftOver.add(d.id()));
uiTopology.nullRegion().reconcileDevices(leftOver);
// now that we have correct region hierarchy, and devices are in their
// respective regions, we can compute synthetic links for each region.
uiTopology.computeSynthLinks();
}
// === CACHE STATISTICS
/**
......@@ -583,12 +601,21 @@ class ModelCache {
}
/**
* Returns the number of links in the topology.
* Returns the number of device links in the topology.
*
* @return number of links
* @return number of device links
*/
public int linkCount() {
return uiTopology.linkCount();
public int deviceLinkCount() {
return uiTopology.deviceLinkCount();
}
/**
* Returns the number of edge links in the topology.
*
* @return number of edge links
*/
public int edgeLinkCount() {
return uiTopology.edgeLinkCount();
}
/**
......@@ -600,4 +627,12 @@ class ModelCache {
return uiTopology.hostCount();
}
/**
* Returns the number of synthetic links in the topology.
*
* @return the number of synthetic links
*/
public int synthLinkCount() {
return uiTopology.synthLinkCount();
}
}
......
......@@ -65,9 +65,10 @@ import org.onosproject.ui.impl.topo.UiTopoSession;
import org.onosproject.ui.model.ServiceBundle;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiDevice;
import org.onosproject.ui.model.topo.UiDeviceLink;
import org.onosproject.ui.model.topo.UiHost;
import org.onosproject.ui.model.topo.UiLink;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiSynthLink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -269,12 +270,22 @@ public final class UiSharedTopologyModel
}
/**
* Returns the set of links stored in the model cache.
* Returns the set of device links stored in the model cache.
*
* @return set of links
* @return set of device links
*/
public Set<UiLink> getLinks() {
return cache.getAllLinks();
public Set<UiDeviceLink> getDeviceLinks() {
return cache.getAllDeviceLinks();
}
/**
* Returns the synthetic links associated with the specified region.
*
* @param regionId region ID
* @return synthetic links for that region
*/
public List<UiSynthLink> getSynthLinks(RegionId regionId) {
return cache.getSynthLinks(regionId);
}
// =====================================================================
......@@ -434,11 +445,11 @@ public final class UiSharedTopologyModel
case LINK_ADDED:
case LINK_UPDATED:
cache.addOrUpdateLink(link);
cache.addOrUpdateDeviceLink(link);
break;
case LINK_REMOVED:
cache.removeLink(link);
cache.removeDeviceLink(link);
break;
default:
......
......@@ -29,9 +29,9 @@ import org.onosproject.net.region.Region;
import org.onosproject.ui.impl.topo.model.UiModelEvent.Type;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiDevice;
import org.onosproject.ui.model.topo.UiDeviceLink;
import org.onosproject.ui.model.topo.UiElement;
import org.onosproject.ui.model.topo.UiHost;
import org.onosproject.ui.model.topo.UiLink;
import org.onosproject.ui.model.topo.UiLinkId;
import org.onosproject.ui.model.topo.UiRegion;
......@@ -265,54 +265,54 @@ public class ModelCacheTest extends AbstractTopoModelTest {
// we've established that the ID is the same for both
UiLinkId linkId = idA2B;
cache.addOrUpdateLink(link1);
cache.addOrUpdateDeviceLink(link1);
dispatcher.assertLast(Type.LINK_ADDED_OR_UPDATED, linkId.toString());
dispatcher.assertEventCount(1);
assertEquals("unex # links", 1, cache.linkCount());
assertEquals("unex # links", 1, cache.deviceLinkCount());
UiLink link = cache.accessLink(linkId);
UiDeviceLink link = cache.accessDeviceLink(linkId);
assertEquals("dev A not d2", DEVID_2, link.deviceA());
assertEquals("dev B not d7", DEVID_7, link.deviceB());
assertEquals("wrong backing link A-B", link1, link.linkAtoB());
assertEquals("backing link B-A?", null, link.linkBtoA());
cache.addOrUpdateLink(link2);
cache.addOrUpdateDeviceLink(link2);
dispatcher.assertLast(Type.LINK_ADDED_OR_UPDATED, linkId.toString());
dispatcher.assertEventCount(2);
// NOTE: yes! expect 1 UiLink
assertEquals("unex # links", 1, cache.linkCount());
assertEquals("unex # links", 1, cache.deviceLinkCount());
link = cache.accessLink(linkId);
link = cache.accessDeviceLink(linkId);
assertEquals("dev A not d2", DEVID_2, link.deviceA());
assertEquals("dev B not d7", DEVID_7, link.deviceB());
assertEquals("wrong backing link A-B", link1, link.linkAtoB());
assertEquals("wrong backing link B-A", link2, link.linkBtoA());
// now remove links one at a time
cache.removeLink(link1);
cache.removeDeviceLink(link1);
// NOTE: yes! ADD_OR_UPDATE, since the link was updated
dispatcher.assertLast(Type.LINK_ADDED_OR_UPDATED, linkId.toString());
dispatcher.assertEventCount(3);
// NOTE: yes! expect 1 UiLink (still)
assertEquals("unex # links", 1, cache.linkCount());
assertEquals("unex # links", 1, cache.deviceLinkCount());
link = cache.accessLink(linkId);
link = cache.accessDeviceLink(linkId);
assertEquals("dev A not d2", DEVID_2, link.deviceA());
assertEquals("dev B not d7", DEVID_7, link.deviceB());
assertEquals("backing link A-B?", null, link.linkAtoB());
assertEquals("wrong backing link B-A", link2, link.linkBtoA());
// remove final link
cache.removeLink(link2);
cache.removeDeviceLink(link2);
dispatcher.assertLast(Type.LINK_REMOVED, linkId.toString());
dispatcher.assertEventCount(4);
// NOTE: finally link should be removed from cache
assertEquals("unex # links", 0, cache.linkCount());
assertEquals("unex # links", 0, cache.deviceLinkCount());
}
private void assertHostLinkCounts(int nHosts, int nLinks) {
assertEquals("unex # hosts", nHosts, cache.hostCount());
assertEquals("unex # links", nLinks, cache.linkCount());
assertEquals("unex # links", nLinks, cache.edgeLinkCount());
}
private void assertLocation(HostId hid, DeviceId expDev, int expPort) {
......@@ -403,6 +403,8 @@ public class ModelCacheTest extends AbstractTopoModelTest {
assertEquals("unex # regions", 3, cache.regionCount());
assertEquals("unex # devices", 9, cache.deviceCount());
assertEquals("unex # hosts", 18, cache.hostCount());
assertEquals("unex # hosts", 26, cache.linkCount());
assertEquals("unex # device-links", 8, cache.deviceLinkCount());
assertEquals("unex # edge-links", 18, cache.edgeLinkCount());
assertEquals("unex # synth-links", 0, cache.synthLinkCount());
}
}
......