Simon Hunt
Committed by Gerrit Code Review

ONOS-4326: Working on topology topo2start processing.

- Added getPeers() to UiTopoLayoutService.
- Fixed wipe-out command to leave the default layout alone.
- Fixed handling of null-region (associated with default layout).
- Added refresh() method to model cache.
- Fixed regions-topo-2 device IDs

Change-Id: Iee49b47ff6702bed9751be7b63392577422d4763
......@@ -115,7 +115,12 @@ public class WipeOutCommand extends ClustersListCommand {
private void wipeOutLayouts() {
print("Wiping UI layouts");
UiTopoLayoutService service = get(UiTopoLayoutService.class);
service.getLayouts().forEach(service::removeLayout);
// wipe out all layouts except the default, which should always be there
service.getLayouts().forEach(l -> {
if (!l.id().isDefault()) {
service.removeLayout(l);
}
});
}
private void wipeOutRegions() {
......
......@@ -57,6 +57,15 @@ public interface UiTopoLayoutService {
UiTopoLayout getLayout(UiTopoLayoutId layoutId);
/**
* Returns the set of peer layouts of the specified layout. That is,
* those layouts that share the same parent.
*
* @param layoutId layout identifier
* @return set of peer layouts; empty set if layout has no peers
*/
Set<UiTopoLayout> getPeers(UiTopoLayoutId layoutId);
/**
* Returns the set of the child layouts of the specified layout.
*
* @param layoutId layout identifier
......
......@@ -29,12 +29,27 @@ import java.util.List;
import java.util.Set;
import static com.google.common.base.MoreObjects.toStringHelper;
import static org.onosproject.net.region.RegionId.regionId;
/**
* Represents a region.
*/
public class UiRegion extends UiNode {
private static final String NULL_NAME = "<null-region>";
/**
* The identifier for the null-region. That is, a container for devices,
* hosts, and links for those that belong to no region.
*/
public static final RegionId NULL_ID = regionId(NULL_NAME);
private static final String[] DEFAULT_LAYER_TAGS = {
UiNode.LAYER_OPTICAL,
UiNode.LAYER_PACKET,
UiNode.LAYER_DEFAULT
};
// loose bindings to things in this region
private final Set<DeviceId> deviceIds = new HashSet<>();
private final Set<HostId> hostIds = new HashSet<>();
......@@ -53,10 +68,12 @@ public class UiRegion extends UiNode {
* @param region backing region
*/
public UiRegion(UiTopology topology, Region region) {
// Implementation Note: if region is null, this UiRegion is being used
// as a container for devices, hosts, links that belong to no region.
this.topology = topology;
this.region = region;
// unless told otherwise, we'll use a single, default layer
layerOrder.add(UiNode.LAYER_DEFAULT);
setLayerOrder(DEFAULT_LAYER_TAGS);
}
@Override
......@@ -83,7 +100,7 @@ public class UiRegion extends UiNode {
* @return region ID
*/
public RegionId id() {
return region.id();
return region == null ? NULL_ID : region.id();
}
@Override
......@@ -93,11 +110,12 @@ public class UiRegion extends UiNode {
@Override
public String name() {
return region.name();
return region == null ? NULL_NAME : region.name();
}
/**
* Returns the region instance backing this UI region.
* Returns the region instance backing this UI region. If this instance
* represents the "null-region", the value returned will be null.
*
* @return the backing region instance
*/
......@@ -132,7 +150,17 @@ public class UiRegion extends UiNode {
* @return region type
*/
public Region.Type type() {
return region.type();
return region == null ? null : region.type();
}
/**
* Returns the count of devices in this region.
*
* @return the device count
*/
public int deviceCount() {
return deviceIds.size();
}
/**
......@@ -195,7 +223,7 @@ public class UiRegion extends UiNode {
* optical layer should be rendered "below" nodes in the packet layer,
* this method should return:
* <pre>
* [UiNode.LAYER_OPTICAL, UiNode.LAYER_PACKET]
* [UiNode.LAYER_OPTICAL, UiNode.LAYER_PACKET, UiNode.LAYER_DEFAULT]
* </pre>
*
* @return layer ordering
......
......@@ -23,10 +23,13 @@ import org.onlab.util.Identifier;
*/
public final class UiTopoLayoutId extends Identifier<String> {
private static final String DEFAULT_STR = "_default_";
/**
* Default topology layout identifier.
*/
public static final UiTopoLayoutId DEFAULT_ID = UiTopoLayoutId.layoutId("_default_");
public static final UiTopoLayoutId DEFAULT_ID =
UiTopoLayoutId.layoutId(DEFAULT_STR);
// For serialization
private UiTopoLayoutId() {
......@@ -45,4 +48,13 @@ public final class UiTopoLayoutId extends Identifier<String> {
public static UiTopoLayoutId layoutId(String value) {
return new UiTopoLayoutId(value);
}
/**
* Returns true if this is the identifier for the default layout.
*
* @return true if this is the default layout identifier
*/
public boolean isDefault() {
return DEFAULT_STR.equals(identifier);
}
}
......
......@@ -61,6 +61,9 @@ public class UiTopology extends UiElement {
private final Map<HostId, UiHost> hostLookup = new HashMap<>();
private final Map<UiLinkId, UiLink> linkLookup = new HashMap<>();
// a container for devices, hosts, etc. belonging to no region
private final UiRegion nullRegion = new UiRegion(this, null);
@Override
public String toString() {
......@@ -89,6 +92,8 @@ public class UiTopology extends UiElement {
deviceLookup.clear();
hostLookup.clear();
linkLookup.clear();
nullRegion.destroy();
}
......@@ -145,6 +150,16 @@ public class UiTopology extends UiElement {
}
/**
* 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.
*
......@@ -186,6 +201,15 @@ public class UiTopology extends UiElement {
}
/**
* 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.
*
......
......@@ -41,18 +41,18 @@ region-add r2 Region2 METRO ${host}
region-add r3 Region3 CAMPUS ${host}
region-add-devices r1 \
of:0000000000000002 \
of:0000000000000003 \
of:0000000000000004
null:0000000000000002 \
null:0000000000000003 \
null:0000000000000004
region-add-devices r2 \
of:0000000000000005 \
of:0000000000000006
null:0000000000000005 \
null:0000000000000006
region-add-devices r3 \
of:0000000000000007 \
of:0000000000000008 \
of:0000000000000009
null:0000000000000007 \
null:0000000000000008 \
null:0000000000000009
regions
......
......@@ -181,7 +181,9 @@ class Topo2Jsonifier {
return payload;
}
payload.put("id", region.idAsString());
payload.set("subregions", jsonSubRegions(subRegions));
if (subRegions != null) {
payload.set("subregions", jsonSubRegions(subRegions));
}
List<String> layerTags = region.layerOrder();
List<Set<UiNode>> splitDevices = splitByLayer(layerTags, region.devices());
......@@ -226,31 +228,6 @@ class Topo2Jsonifier {
return result;
}
/**
* Returns a JSON payload that encapsulates the devices, hosts, links that
* do not belong to any region.
*
* @param oDevices orphan devices
* @param oHosts orphan hosts
* @param oLinks orphan links
* @param layerTags layer tags
* @return a JSON representation of the data
*/
ObjectNode orphans(Set<UiDevice> oDevices, Set<UiHost> oHosts,
Set<UiLink> oLinks, List<String> layerTags) {
ObjectNode payload = objectNode();
List<Set<UiNode>> splitDevices = splitByLayer(layerTags, oDevices);
List<Set<UiNode>> splitHosts = splitByLayer(layerTags, oHosts);
payload.set("devices", jsonGrouped(splitDevices));
payload.set("hosts", jsonGrouped(splitHosts));
payload.set("links", jsonLinks(oLinks));
payload.set("layerOrder", jsonStrings(layerTags));
return payload;
}
private ObjectNode json(UiNode node) {
if (node instanceof UiRegion) {
......@@ -270,7 +247,7 @@ class Topo2Jsonifier {
.put("id", device.idAsString())
.put("type", device.type())
.put("online", device.isOnline())
.put("master", device.master().toString())
.put("master", nullIsEmpty(device.master()))
.put("layer", device.layer());
// TODO: complete device details
......@@ -303,7 +280,8 @@ class Topo2Jsonifier {
private ObjectNode jsonClosedRegion(UiRegion region) {
return objectNode()
.put("id", region.idAsString());
.put("id", region.idAsString())
.put("nDevs", region.deviceCount());
// TODO: complete closed-region details
}
......
......@@ -17,7 +17,6 @@
package org.onosproject.ui.impl.topo;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.onlab.osgi.ServiceDirectory;
import org.onosproject.ui.RequestHandler;
......@@ -25,9 +24,6 @@ import org.onosproject.ui.UiConnection;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.impl.UiWebSocket;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiDevice;
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.UiTopoLayout;
import org.slf4j.Logger;
......@@ -37,8 +33,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Set;
import static org.onosproject.ui.model.topo.UiNode.LAYER_DEFAULT;
/*
NOTES:
......@@ -69,7 +63,6 @@ public class Topo2ViewMessageHandler extends UiMessageHandler {
private static final String CURRENT_LAYOUT = "topo2CurrentLayout";
private static final String CURRENT_REGION = "topo2CurrentRegion";
private static final String PEER_REGIONS = "topo2PeerRegions";
private static final String ORPHANS = "topo2Orphans";
private static final String TOPO_START_DONE = "topo2StartDone";
......@@ -109,6 +102,12 @@ public class Topo2ViewMessageHandler extends UiMessageHandler {
log.debug("topo2Start: {}", payload);
// this may be a little heavyweight, but it might be safer to do
// this than make assumptions about the order in which devices
// and regions are added... and thus internal linkages set up
// correctly
topoSession.refreshModel();
// this is the list of ONOS cluster members
List<UiClusterMember> instances = topoSession.getAllInstances();
sendMessage(ALL_INSTANCES, t2json.instances(instances));
......@@ -131,14 +130,7 @@ public class Topo2ViewMessageHandler extends UiMessageHandler {
peersPayload.set("peers", t2json.closedRegions(peers));
sendMessage(PEER_REGIONS, peersPayload);
// return devices, hosts, links belonging to no region
Set<UiDevice> oDevices = topoSession.getOrphanDevices();
Set<UiHost> oHosts = topoSession.getOrphanHosts();
Set<UiLink> oLinks = topoSession.getOrphanLinks();
List<String> oLayers = getOrphanLayerOrder();
sendMessage(ORPHANS, t2json.orphans(oDevices, oHosts, oLinks, oLayers));
// finally, tell the UI that we are done
// finally, tell the UI that we are done : TODO review / delete??
sendMessage(TOPO_START_DONE, null);
......@@ -154,14 +146,6 @@ public class Topo2ViewMessageHandler extends UiMessageHandler {
}
// TODO: we need to decide on how this should really get populated.
// For example, to be "backward compatible", this should really be
// [ LAYER_OPTICAL, LAYER_PACKET, LAYER_DEFAULT ]
private List<String> getOrphanLayerOrder() {
// NOTE that LAYER_DEFAULT must always be last in the array
return ImmutableList.of(LAYER_DEFAULT);
}
private final class Topo2Stop extends RequestHandler {
private Topo2Stop() {
super(TOPO2_STOP);
......
......@@ -34,6 +34,7 @@ import org.onosproject.ui.model.topo.UiTopoLayoutId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
......@@ -110,6 +111,21 @@ public class UiTopoLayoutManager implements UiTopoLayoutService {
}
@Override
public Set<UiTopoLayout> getPeers(UiTopoLayoutId layoutId) {
checkNotNull(layoutId, ID_NULL);
UiTopoLayout layout = layoutMap.get(layoutId);
if (layout == null) {
return Collections.emptySet();
}
UiTopoLayoutId parentId = layout.parent();
return layoutMap.values().stream()
.filter(l -> !Objects.equals(l.id(), layoutId) &&
Objects.equals(l.parent(), parentId))
.collect(Collectors.toSet());
}
@Override
public Set<UiTopoLayout> getChildren(UiTopoLayoutId layoutId) {
checkNotNull(layoutId, ID_NULL);
return layoutMap.values().stream()
......
......@@ -16,21 +16,19 @@
package org.onosproject.ui.impl.topo;
import org.onosproject.net.region.RegionId;
import org.onosproject.ui.UiTopoLayoutService;
import org.onosproject.ui.impl.UiWebSocket;
import org.onosproject.ui.impl.topo.model.UiModelEvent;
import org.onosproject.ui.impl.topo.model.UiModelListener;
import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
import org.onosproject.ui.model.topo.UiClusterMember;
import org.onosproject.ui.model.topo.UiDevice;
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.UiTopoLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
......@@ -167,23 +165,23 @@ public class UiTopoSession implements UiModelListener {
* @return region that the layout is based upon
*/
public UiRegion getRegion(UiTopoLayout layout) {
return sharedModel.getRegion(layout.regionId());
RegionId rid = layout.regionId();
return rid == null ? sharedModel.getNullRegion() : sharedModel.getRegion(rid);
}
/**
* Returns the regions that are "peers" to this region. That is, based on
* the layout the user is viewing, all the regions that are associated with
* layouts that are children of the parent layout to this layout.
* layouts that share the same parent layout as this layout.
*
* @param layout the layout being viewed
* @return all regions that are "siblings" to this layout's region
*/
public Set<UiRegion> getPeerRegions(UiTopoLayout layout) {
UiRegion currentRegion = getRegion(layout);
// TODO: consult topo layout service to get hierarchy info...
// TODO: then consult shared model to get regions
return Collections.emptySet();
Set<UiTopoLayout> peerLayouts = layoutService.getPeers(layout.id());
Set<UiRegion> peers = new HashSet<>();
peerLayouts.forEach(l -> peers.add(sharedModel.getRegion(l.regionId())));
return peers;
}
/**
......@@ -193,42 +191,16 @@ public class UiTopoSession implements UiModelListener {
* @return all regions that are "contained within" this layout's region
*/
public Set<UiRegion> getSubRegions(UiTopoLayout layout) {
UiRegion currentRegion = getRegion(layout);
// TODO: consult topo layout service to get child layouts...
// TODO: then consult shared model to get regions
return Collections.emptySet();
Set<UiTopoLayout> kidLayouts = layoutService.getChildren(layout.id());
Set<UiRegion> kids = new HashSet<>();
kidLayouts.forEach(l -> kids.add(sharedModel.getRegion(l.regionId())));
return kids;
}
/**
* Returns all devices that are not in a region.
*
* @return all devices not in a region
* Refreshes the model's internal state.
*/
public Set<UiDevice> getOrphanDevices() {
// TODO: get devices with no region
return Collections.emptySet();
public void refreshModel() {
sharedModel.refresh();
}
/**
* Returns all hosts that are not in a region.
*
* @return all hosts not in a region
*/
public Set<UiHost> getOrphanHosts() {
// TODO: get hosts with no region
return Collections.emptySet();
}
/**
* Returns all links that are not in a region.
*
* @return all links not in a region
*/
public Set<UiLink> getOrphanLinks() {
// TODO: get links with no region
return Collections.emptySet();
}
}
......
......@@ -41,6 +41,7 @@ import org.onosproject.ui.model.topo.UiTopology;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
......@@ -169,18 +170,38 @@ class ModelCache {
// TODO: post event
}
// === THE NULL REGION
UiRegion nullRegion() {
return uiTopology.nullRegion();
}
// === REGIONS
private UiRegion addNewRegion(Region r) {
UiRegion region = new UiRegion(uiTopology, r);
uiTopology.add(region);
log.debug("Region {} added to topology", region);
return region;
}
private void updateRegion(UiRegion region) {
Set<DeviceId> devs = services.region().getRegionDevices(region.id());
region.reconcileDevices(devs);
RegionId rid = region.id();
Set<DeviceId> deviceIds = services.region().getRegionDevices(rid);
// Make sure device objects refer to their region
deviceIds.forEach(d -> {
UiDevice dev = uiTopology.findDevice(d);
if (dev != null) {
dev.setRegionId(rid);
} else {
// if we don't have the UiDevice in the topology, what can we do?
log.warn("Region device {}, but we don't have UiDevice in topology", d);
}
});
// Make sure the region object refers to the devices
region.reconcileDevices(deviceIds);
}
private void loadRegions() {
......@@ -224,21 +245,22 @@ class ModelCache {
private UiDevice addNewDevice(Device d) {
UiDevice device = new UiDevice(uiTopology, d);
updateDevice(device);
uiTopology.add(device);
log.debug("Device {} added to topology", device);
return device;
}
// make sure the UiDevice is tagged with the region it belongs to
private void updateDevice(UiDevice device) {
Region regionForDevice = services.region().getRegionForDevice(device.id());
if (regionForDevice != null) {
device.setRegionId(regionForDevice.id());
}
Region r = services.region().getRegionForDevice(device.id());
RegionId rid = r == null ? UiRegion.NULL_ID : r.id();
device.setRegionId(rid);
}
private void loadDevices() {
for (Device d : services.device().getDevices()) {
UiDevice device = addNewDevice(d);
updateDevice(device);
addNewDevice(d);
}
}
......@@ -248,8 +270,9 @@ class ModelCache {
UiDevice uiDevice = uiTopology.findDevice(id);
if (uiDevice == null) {
uiDevice = addNewDevice(device);
} else {
updateDevice(uiDevice);
}
updateDevice(uiDevice);
postEvent(DEVICE_ADDED_OR_UPDATED, uiDevice);
}
......@@ -434,6 +457,43 @@ class ModelCache {
}
/**
* Refreshes the internal state.
*/
public void refresh() {
// fix up internal linkages if they aren't correct
// at the moment, this is making sure devices are in the correct region
Set<UiDevice> allDevices = uiTopology.allDevices();
services.region().getRegions().forEach(r -> {
RegionId rid = r.id();
UiRegion region = uiTopology.findRegion(rid);
if (region != null) {
Set<DeviceId> deviceIds = services.region().getRegionDevices(rid);
region.reconcileDevices(deviceIds);
deviceIds.forEach(devId -> {
UiDevice dev = uiTopology.findDevice(devId);
if (dev != null) {
dev.setRegionId(r.id());
allDevices.remove(dev);
} else {
log.warn("Region device ID {} but no UiDevice in topology",
devId);
}
});
} else {
log.warn("No UiRegion in topology for ID {}", rid);
}
});
// what is left over, must belong to the null-region
Set<DeviceId> leftOver = new HashSet<>(allDevices.size());
allDevices.forEach(d -> leftOver.add(d.id()));
uiTopology.nullRegion().reconcileDevices(leftOver);
}
// === CACHE STATISTICS
/**
......
......@@ -217,6 +217,22 @@ public final class UiSharedTopologyModel
return cache.accessRegion(id);
}
/**
* Returns the null region.
*
* @return the null region
*/
public UiRegion getNullRegion() {
return cache.nullRegion();
}
/**
* Refreshes the cache's internal state.
*/
public void refresh() {
cache.refresh();
}
// =====================================================================
......
{
"event": "topo2CurrentRegion",
"payload": {
"note": "no-region"
"id": "<null-region>",
"subregions": [{
"id": "r2",
"nDevs": 2
}, {
"id": "r1",
"nDevs": 3
}],
"devices": [
[],
[],
[{
"id": "null:0000000000000001",
"type": "switch",
"online": false,
"master": "",
"layer": "def"
}]
],
"hosts": [
[],
[],
[]
],
"links": [],
"layerOrder": ["opt", "pkt", "def"]
}
}
......
{
"event": "topo2Orphans",
"payload": {
"devices": [
[]
],
"hosts": [
[]
],
"links": [],
"layerOrder": ["def"]
}
}